HelloJohn / docs
SDKsGo SDK

Verify Token

Verify HelloJohn JWTs in Go — local JWKS verification, claims extraction, and token validation patterns.

Verify Token

Verify HelloJohn access tokens in Go backends using the official SDK or directly with go-jose.


Using the SDK

import "github.com/hellojohn/hellojohn-go"

hj := hellojohn.New(hellojohn.Config{
    TenantID:  os.Getenv("HELLOJOHN_TENANT_ID"),
    SecretKey: os.Getenv("HELLOJOHN_SECRET_KEY"),
})

VerifyToken

payload, err := hj.VerifyToken(ctx, token)
if err != nil {
    // Invalid, expired, or malformed token
    http.Error(w, "Unauthorized", http.StatusUnauthorized)
    return
}

fmt.Println(payload.Sub) // User ID (sub claim)

Payload fields:

type TokenPayload struct {
    Sub     string    `json:"sub"`      // User ID
    Sid     string    `json:"sid"`      // Session ID
    Iss     string    `json:"iss"`      // Issuer
    Aud     string    `json:"aud"`      // Audience (tenant ID)
    Iat     int64     `json:"iat"`      // Issued at
    Exp     int64     `json:"exp"`      // Expiry
    OrgID   string    `json:"org_id"`   // Active org ID (if any)
    OrgRole string    `json:"org_role"` // Role in active org
}

HTTP Middleware

func AuthMiddleware(hj *hellojohn.Client) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            authHeader := r.Header.Get("Authorization")
            if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
                http.Error(w, `{"error":"missing token"}`, http.StatusUnauthorized)
                return
            }

            token := strings.TrimPrefix(authHeader, "Bearer ")
            payload, err := hj.VerifyToken(r.Context(), token)
            if err != nil {
                http.Error(w, `{"error":"invalid token"}`, http.StatusUnauthorized)
                return
            }

            // Store user ID in request context
            ctx := context.WithValue(r.Context(), userIDKey, payload.Sub)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// Retrieve from context
func getUserID(r *http.Request) string {
    return r.Context().Value(userIDKey).(string)
}

With net/http

mux := http.NewServeMux()

// Public
mux.HandleFunc("GET /api/health", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte(`{"ok":true}`))
})

// Protected — wrap with middleware
protected := AuthMiddleware(hj)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    userID := getUserID(r)
    json.NewEncoder(w).Encode(map[string]string{"userId": userID})
}))

mux.Handle("GET /api/me", protected)

Without the SDK (using go-jose)

import (
    "github.com/go-jose/go-jose/v4"
    "github.com/go-jose/go-jose/v4/jwt"
)

// Fetch JWKS once and cache
jwksURL := fmt.Sprintf(
    "https://api.hellojohn.dev/v1/jwks/%s",
    os.Getenv("HELLOJOHN_TENANT_ID"),
)

// Parse and verify
keySet := // fetch and parse JWKS...

tok, err := jwt.ParseSigned(tokenStr, []jose.SignatureAlgorithm{jose.EdDSA})
if err != nil {
    // invalid
}

var claims jwt.Claims
if err := tok.Claims(keySet, &claims); err != nil {
    // signature invalid
}

if err := claims.ValidateWithLeeway(jwt.Expected{
    Issuer:   "https://api.hellojohn.dev",
    Audience: jwt.Audience{os.Getenv("HELLOJOHN_TENANT_ID")},
    Time:     time.Now(),
}, time.Second*30); err != nil {
    // expired or wrong issuer/audience
}

On this page