How to use Kinde with golang OSS libraries

By Evgeny Komarevtsev — Published

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.

Kinde configuration

Link to this section

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)

Golang configuration

Link to this section

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.

Create the golang CLI

Link to this section

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

Request the token from Kinde

Link to this section

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.