How to use Kinde with golang OSS libraries
By Evgeny Komarevtsev —
Machine-to-machine tokens, also known as M2M tokens, serve as security keys within OAuth 2.0 authorization flows, between two machines or applications. These tokens enable secure data exchange without the need for human involvement.
There are multiple use cases for authorization, when you wouldn’t want to involve users, such as authorizing an external provider to call your API.
Another example is to make secure calls between your services or microservices.
First, register a Kinde account for free and get your Kinde domain.
In Kinde, add a M2M application. (Settings > Applications)
Register and enable an API for the new application. (Settings > APIs)
Back in the M2M application you created, connect the API you just registered. (Settings > Applications > View details > APIs)
Now we’re ready to create a simple golang command-line app. I’m using established OSS libraries for this part, because there is no native golang support.
-
jwt-go
- MIT, GitHub https://github.com/golang-jwt/jwt -
keyfunc
- Apache 2.0, GitHub https://github.com/MicahParks/keyfunc -
GoDotEnv
- MIT, GitHub https://github.com/joho/godotenv
Create an empty folder and run the following:
go mod init kinde_test
go get github.com/MicahParks/keyfunc/v2 github.com/golang-jwt/jwt/v5 github.com/joho/godotenv golang.org/x/oauth2
touch main.go
touch .env
Now open the folder with your editor of choice and open .env
.
KINDE_ENVIRONMENT_DOMAIN=<tou_kinde_domain>.kinde.com
KINDE_M2M_CLIENT_ID=<M2M client ID from admin UI>
KINDE_M2M_CLIENT_SECRET=<M2M client secret from the admin UI>
MY_API_AUDIENCE=https://my.website/api
In this example, I’m using using the client_credentials
flow, authorizing the API, which is represented by https://my.website/api
audience.
package main
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/joho/godotenv"
"golang.org/x/oauth2/clientcredentials"
)
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
apiAudience := os.Getenv("MY_API_AUDIENCE")
tokenIssuer := fmt.Sprintf("https://%v", os.Getenv("KINDE_ENVIRONMENT_DOMAIN"))
config := clientcredentials.Config{
ClientID: os.Getenv("KINDE_M2M_CLIENT_ID"),
ClientSecret: os.Getenv("KINDE_M2M_CLIENT_SECRET"),
TokenURL: fmt.Sprintf("%v/oauth2/token", tokenIssuer),
Scopes: []string{},
EndpointParams: map[string][]string{
"audience": {apiAudience},
},
}
context, _ := context.WithTimeout(context.Background(), 5*time.Second)
m2mToken, err := config.Token(context)
if err != nil {
fmt.Printf("%v", err)
}
verifytoken(m2mToken, tokenIssuer, apiAudience)
}
We can also use the client, generated from configuration to call the API, which attaches the correct authorization token to the outgoing call.
response, err := config.Client(context).Get("https://my.website.com/api/users")
Here’s how you can secure your receiving API.
package main
import (
"fmt"
"github.com/MicahParks/keyfunc/v2"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/oauth2"
)
func verifytoken(m2mToken *oauth2.Token, tokenIssuer string, audience string) {
jwksURL := fmt.Sprintf("%v/.well-known/jwks", tokenIssuer)
jwks, _ := keyfunc.Get(jwksURL, keyfunc.Options{})
parsedToken, err := jwt.Parse(m2mToken.AccessToken, jwks.Keyfunc,
jwt.WithValidMethods([]string{"RS256"}), //verifying the signing algorythm
jwt.WithIssuer(tokenIssuer), //verifying the token issuer
jwt.WithAudience(audience)) //verifying that the token is for correct audience
if err != nil {
panic(fmt.Errorf("error verifying token %v", err))
}
fmt.Printf("m2m token %#v", parsedToken.Claims)
}
See my other code notes in GitHub Gist.