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 thealg: 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 noexp, 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
- The frontend that produces the token: React, Next.js.
- Other backends: Node.js, Python.
- Deploying behind a strict CSP: Content Security Policy.