Docs

Verify a Clerk session in Go

There are two ways to verify a session with Clerk in your Go application:

  • Using Clerk middleware
    If you want to verify a session in an HTTP context, it is recommended to use Clerk middleware. Clerk middleware guarantees better performance and efficiency by making the minimum necessary requests to the Clerk Backend API.

  • Manually verifying the session token
    If you want to verify a session in a non-HTTP context, or if you would like total control over the verification process, you can verify the session token on your own.

Use Clerk middleware to verify a session

Go enables you to create a simple HTTP server, and Clerk enables you to authenticate any request. Together, you can create a secure server that only allows authenticated users to access certain routes.

Clerk Go SDK provides two functions for adding authentication to HTTP handlers:

Both middleware functions support header based authentication with a bearer token. The token will be parsed, verified, and its claims will be extracted as SessionClaims.

The claims will then be made available in the http.Request.Context for the next handler in the chain. Clerk Go SDK provides the SessionClaimsFromContext() helper for accessing the claims from the context.

The following example demonstrates how to use the WithHeaderAuthorization() middleware to protect a route. If the user tries accessing the route and their session token is valid, the user's ID and banned status will be returned in the response.

Note

Your Clerk Secret Key is required. If you are signed into the Clerk Dashboard, your Secret Key should become visible by selecting the eye icon. Otherwise, you can retrieve your Clerk Secret Key on the API keys page in the Clerk Dashboard.

main.go
package main

import (
  "fmt"
  "net/http"

  "github.com/clerk/clerk-sdk-go/v2"
  clerkhttp "github.com/clerk/clerk-sdk-go/v2/http"
  "github.com/clerk/clerk-sdk-go/v2/user"
)

func main() {
  clerk.SetKey("YOUR_SECRET_KEY")

  mux := http.NewServeMux()
  mux.HandleFunc("/", publicRoute)
  protectedHandler := http.HandlerFunc(protectedRoute)
  mux.Handle(
    "/protected",
    clerkhttp.WithHeaderAuthorization()(protectedHandler),
  )

  http.ListenAndServe(":3000", mux)
}

func publicRoute(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte(`{"access": "public"}`))
}

func protectedRoute(w http.ResponseWriter, r *http.Request) {
  claims, ok := clerk.SessionClaimsFromContext(r.Context())
  if !ok {
    w.WriteHeader(http.StatusUnauthorized)
    w.Write([]byte(`{"access": "unauthorized"}`))
    return
  }

  usr, err := user.Get(r.Context(), claims.Subject)
  if err != nil {
    // handle the error
  }
  fmt.Fprintf(w, `{"user_id": "%s", "user_banned": "%t"}`, usr.ID, usr.Banned)
}

Manually verify a session token

Verifying a Clerk session manually is useful when you want to verify a session in a non-HTTP context, or if you would like total control over the verification process. With the solution above, Clerk middleware makes the minimum necessary requests to the Clerk Backend API. With this solution, you must be mindful of API rate limits.

Verifying a session token requires providing a JSON Web Key. When using Clerk middleware to verify a session, it fetches the JSON Web Key once and caches it for you. However, when manually verifying a session, there's no caching layer for the JSON Web Key.

Clerk Go SDK provides a set of functions for decoding and verifying JWTs, as well as fetching JSON Web Keys. It is recommended to cache your JSON Web Key and invalidate the cache only when a replacement key is generated.

The following example demonstrates how to manually verify a session token. If the user tries accessing the route and their session token is valid, the user's ID and banned status will be returned in the response.

main.go
package main

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/clerk/clerk-sdk-go/v2"
	"github.com/clerk/clerk-sdk-go/v2/jwt"
	"github.com/clerk/clerk-sdk-go/v2/user"
)

func main() {
  clerk.SetKey("YOUR_SECRET_KEY")

	mux := http.NewServeMux()
	mux.HandleFunc("/", publicRoute)
	mux.HandleFunc("/protected", protectedRoute)

	http.ListenAndServe(":3000", mux)
}

func publicRoute(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(`{"access": "public"}`))
}

func protectedRoute(w http.ResponseWriter, r *http.Request) {
	// Get the session JWT from the Authorization header
	sessionToken := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")

	// Verify the session
	claims, err := jwt.Verify(r.Context(), &jwt.VerifyParams{
		Token: sessionToken,
	})
	if err != nil {
		// handle the error
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte(`{"access": "unauthorized"}`))
		return
	}

	usr, err := user.Get(r.Context(), claims.Subject)
	if err != nil {
		// handle the error
	}
	fmt.Fprintf(w, `{"user_id": "%s", "user_banned": "%t"}`, usr.ID, usr.Banned)
}

If you need to verify session tokens frequently, it's recommended to cache the JSON Web Key for your instance in order to avoid making too many requests to the Clerk Backend API.

The following example includes the same code as above, with the addition of demonstrating a possible way to store your JSON Web Key. The example is meant to serve as a guide; implementation may vary depending on your needs.

main.go
package main

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/clerk/clerk-sdk-go/v2"
	"github.com/clerk/clerk-sdk-go/v2/jwks"
	"github.com/clerk/clerk-sdk-go/v2/jwt"
	"github.com/clerk/clerk-sdk-go/v2/user"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", publicRoute)

	// Initialize storage for JSON Web Keys. You can cache/store
	// the key for as long as it's valid, and pass it to jwt.Verify.
	// This way jwt.Verify won't make requests to the Clerk
	// Backend API to refetch the JSON Web Key.
	// Make sure you refetch the JSON Web Key whenever your
	// Clerk Secret Key changes.
	jwkStore := NewJWKStore()

	config := &clerk.ClientConfig{}
	config.Key = clerk.String("YOUR_SECRET_KEY")
	jwksClient := jwks.NewClient(config)
	mux.HandleFunc("/protected", protectedRoute(jwksClient, jwkStore))

	http.ListenAndServe(":3000", mux)
}

func publicRoute(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(`{"access": "public"}`))
}

func protectedRoute(jwksClient *jwks.Client, store JWKStore) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		// Get the session JWT from the Authorization header
		sessionToken := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")

		// Attempt to get the JSON Web Key from your store.
		jwk := store.GetJWK()
		if jwk == nil {
			// Decode the session JWT so that we can find the key ID.
			unsafeClaims, err := jwt.Decode(r.Context(), &jwt.DecodeParams{
				Token: sessionToken,
			})
			if err != nil {
				// handle the error
				w.WriteHeader(http.StatusUnauthorized)
				w.Write([]byte(`{"access": "unauthorized"}`))
				return
			}

			// Fetch the JSON Web Key
			jwk, err = jwt.GetJSONWebKey(r.Context(), &jwt.GetJSONWebKeyParams{
				KeyID:      unsafeClaims.KeyID,
				JWKSClient: jwksClient,
			})
			if err != nil {
				// handle the error
				w.WriteHeader(http.StatusUnauthorized)
				w.Write([]byte(`{"access": "unauthorized"}`))
				return
			}
		}
		// Write the JSON Web Key to your store, so that next time
		// you can use the cached value.
		store.SetJWK(jwk)

		// Verify the session
		claims, err := jwt.Verify(r.Context(), &jwt.VerifyParams{
			Token: sessionToken,
			JWK:   jwk,
		})
		if err != nil {
			// handle the error
			w.WriteHeader(http.StatusUnauthorized)
			w.Write([]byte(`{"access": "unauthorized"}`))
			return
		}

		usr, err := user.Get(r.Context(), claims.Subject)
		if err != nil {
			// handle the error
		}
		fmt.Fprintf(w, `{"user_id": "%s", "user_banned": "%t"}`, usr.ID, usr.Banned)
	}
}

// Sample interface for JSON Web Key storage.
// Implementation may vary.
type JWKStore interface {
	GetJWK() *clerk.JSONWebKey
	SetJWK(*clerk.JSONWebKey)
}

func NewJWKStore() JWKStore {
	// Implementation may vary. This can be an
	// in-memory store, database, caching layer,...
	return nil
}

Feedback

What did you think of this content?

Last updated on