Backend & Token Verification

Verifying JWTs in Go

Validate EmbedAuth's RS256 tokens in Go using the golang-jwt and keyfunc libraries, with a cached JWKS, strict claim validation, and net/http middleware.

1 min readUpdated July 5, 2026

This guide verifies EmbedAuth's RS256 tokens in Go using golang-jwt/jwt together with MicahParks/keyfunc, which keeps EmbedAuth's JWKS fetched and refreshed in the background.

Install

go get github.com/golang-jwt/jwt/v5
go get github.com/MicahParks/keyfunc/v3

The verifier

package auth

import (
	"context"
	"fmt"

	"github.com/MicahParks/keyfunc/v3"
	"github.com/golang-jwt/jwt/v5"
)

const (
	clientID = "YOUR_CLIENT_ID"
	issuer   = "https://embedauth.com"
)

var jwksURL = fmt.Sprintf("%s/.well-known/jwks.json?clientId=%s", issuer, clientID)

// NewVerifier builds a keyfunc that refreshes the JWKS in the background.
func NewVerifier(ctx context.Context) (keyfunc.Keyfunc, error) {
	return keyfunc.NewDefaultCtx(ctx, []string{jwksURL})
}

func VerifyToken(k keyfunc.Keyfunc, tokenString string) (jwt.MapClaims, error) {
	claims := jwt.MapClaims{}
	_, err := jwt.ParseWithClaims(tokenString, claims, k.Keyfunc,
		jwt.WithValidMethods([]string{"RS256"}),
		jwt.WithIssuer(issuer),
		jwt.WithAudience(clientID),
		jwt.WithExpirationRequired(),
	)
	if err != nil {
		return nil, err
	}
	return claims, nil
}

ParseWithClaims returns an error unless the signature, iss, aud, and exp all check out. Build the verifier once at startup and share it.

Why each option is present

  • WithValidMethods(["RS256"]) — refuses any token whose header advertises a different algorithm, closing the alg: none / HMAC-confusion class of attacks.
  • WithIssuer — the token must come from EmbedAuth.
  • WithAudience — the token must have been issued for your client, not another EmbedAuth customer's.
  • WithExpirationRequired — rejects tokens with no exp, so a malformed token can never be treated as non-expiring.

net/http middleware

package auth

import (
	"context"
	"net/http"
	"strings"

	"github.com/MicahParks/keyfunc/v3"
)

type ctxKey struct{}

func RequireAuth(k keyfunc.Keyfunc, next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		header := r.Header.Get("Authorization")
		token, ok := strings.CutPrefix(header, "Bearer ")
		if !ok {
			http.Error(w, "missing_token", http.StatusUnauthorized)
			return
		}
		claims, err := VerifyToken(k, token)
		if err != nil {
			http.Error(w, "invalid_token", http.StatusUnauthorized)
			return
		}
		ctx := context.WithValue(r.Context(), ctxKey{}, claims)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

// User pulls the verified claims back out in a handler.
func User(r *http.Request) jwt.MapClaims {
	claims, _ := r.Context().Value(ctxKey{}).(jwt.MapClaims)
	return claims
}

Read the stable user ID with claims["sub"] inside your handlers.

Establishing your own session

As in every language, verification proves identity for a single request. Upsert the user keyed on sub and issue your own session so your app owns session lifetime and revocation, independent of EmbedAuth's token expiry.

Next steps