Skip to main content

Use AI to integrate Auth0

If you use an AI coding assistant like Claude Code, Cursor, or GitHub Copilot, you can add Auth0 API authentication automatically in minutes using agent skills.Install:
npx skills add auth0/agent-skills --skill auth0-quickstart --skill go-jwt-middleware
Then ask your AI assistant:
Add Auth0 JWT authentication to my Go API
Your AI assistant will automatically create your Auth0 API, fetch credentials, install go-jwt-middleware, configure the validator, and protect your API endpoints with JWT validation. Full agent skills documentation →
Prerequisites: Before you begin, ensure you have the following installed:
  • Go 1.24 or newer (required for generics support in go-jwt-middleware v3)
  • Git for version control
Verify installation: go version

Get Started

You’ll build a Go API with three endpoints demonstrating different protection levels: public access, JWT-authenticated, and permission-scoped. The complete implementation uses go-jwt-middleware v3 with Go’s standard net/http library.

View Sample on GitHub

Complete working example with tests
1

Create a new project

Create a new directory for your Go API and initialize a module.
mkdir myapi && cd myapi
go mod init github.com/yourorg/myapi
Install the required dependencies:
go get github.com/auth0/go-jwt-middleware/v3
go get github.com/joho/godotenv
go mod download
Create the project structure:
mkdir -p cmd/server internal/auth internal/config internal/handlers
touch .env cmd/server/main.go internal/config/auth.go internal/auth/claims.go internal/auth/validator.go internal/auth/middleware.go internal/handlers/api.go
go.mod
module github.com/yourorg/myapi

go 1.24

require (
    github.com/auth0/go-jwt-middleware/v3 v3.1.0
    github.com/joho/godotenv v1.5.1
)
2

Setup your Auth0 API

Next, you need to create a new API on your Auth0 tenant and add the environment variables to your project.You have two options to set up your Auth0 API: use a CLI command or configure manually via the Dashboard:
Run the following command in your project’s root directory to create an Auth0 API:
# Install Auth0 CLI (if not already installed)
brew tap auth0/auth0-cli && brew install auth0

# Create Auth0 API
auth0 apis create \
  --name "My Go API" \
  --identifier https://my-go-api.example.com
After creation, copy the Identifier and your Domain values, then create your .env file:
This command will:
  1. Check if you’re authenticated (and prompt for login if needed)
  2. Create an Auth0 API with the specified identifier
  3. Display the API details including the domain and identifier
Security: Never commit .env files to version control. Add .env to your .gitignore file.
3

Define API permissions

Permissions (scopes) let you define how resources can be accessed. For example, grant read access to managers and write access to administrators.
  1. In your API settings, click the Permissions tab
  2. Create the following permission:
PermissionDescription
read:messagesRead messages from the API
This tutorial uses the read:messages scope to protect the scoped endpoint. You can define additional permissions based on your application’s needs.
4

Create configuration loader

Create a config package to load and validate environment variables.
internal/config/auth.go
package config

import (
    "fmt"
    "os"
)

type AuthConfig struct {
    Domain   string
    Audience string
}

func LoadAuthConfig() (*AuthConfig, error) {
    domain := os.Getenv("AUTH0_DOMAIN")
    if domain == "" {
        return nil, fmt.Errorf("AUTH0_DOMAIN environment variable required")
    }

    audience := os.Getenv("AUTH0_AUDIENCE")
    if audience == "" {
        return nil, fmt.Errorf("AUTH0_AUDIENCE environment variable required")
    }

    return &AuthConfig{
        Domain:   domain,
        Audience: audience,
    }, nil
}
What this does:
  • Loads Auth0 domain and audience from environment variables
  • Validates that required configuration is present at startup
  • Returns a type-safe config struct for use across the application
5

Create custom claims and JWT validator

Custom claims allow you to extract and validate application-specific data from JWTs. The validator is the core component that verifies tokens against Auth0.
internal/auth/claims.go
package auth

import (
    "context"
    "fmt"
    "strings"
)

// CustomClaims contains custom data we want to parse from the JWT.
type CustomClaims struct {
    Scope string `json:"scope"`
}

// Validate ensures the custom claims are properly formatted.
func (c *CustomClaims) Validate(ctx context.Context) error {
    if c.Scope == "" {
        return nil
    }

    if strings.TrimSpace(c.Scope) != c.Scope {
        return fmt.Errorf("scope claim has invalid whitespace")
    }

    if strings.Contains(c.Scope, "  ") {
        return fmt.Errorf("scope claim contains double spaces")
    }

    return nil
}

// HasScope checks whether our claims have a specific scope.
func (c *CustomClaims) HasScope(expectedScope string) bool {
    if c.Scope == "" {
        return false
    }

    scopes := strings.Split(c.Scope, " ")
    for _, scope := range scopes {
        if scope == expectedScope {
            return true
        }
    }
    return false
}
Key points:
  • The Validate method is called automatically by the middleware after parsing the JWT
  • HasScope parses space-separated scopes for permission-based access control
  • The validator uses JWKS caching (5 min TTL) and allows 30s clock skew
  • RS256 algorithm is explicitly set to prevent algorithm confusion attacks
6

Create HTTP middleware and handlers

The middleware wraps the validator for HTTP requests. The handlers demonstrate three protection levels: public, private, and permission-scoped.
internal/auth/middleware.go
package auth

import (
    "log/slog"
    "net/http"

    jwtmiddleware "github.com/auth0/go-jwt-middleware/v3"
    "github.com/auth0/go-jwt-middleware/v3/validator"
)

func NewMiddleware(jwtValidator *validator.Validator) (*jwtmiddleware.JWTMiddleware, error) {
    return jwtmiddleware.New(
        jwtmiddleware.WithValidator(jwtValidator),
        jwtmiddleware.WithValidateOnOptions(false),
        jwtmiddleware.WithErrorHandler(func(w http.ResponseWriter, r *http.Request, err error) {
            slog.Error("JWT validation failed", "error", err, "path", r.URL.Path)
            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(http.StatusUnauthorized)
            w.Write([]byte(`{"message":"Failed to validate JWT."}`))
        }),
    )
}
Protection levels:
  • Public (/api/public) — No authentication required
  • Private (/api/private) — Valid JWT required
  • Scoped (/api/private-scoped) — Valid JWT + read:messages permission required
7

Create the main server

Wire everything together in the main entry point with production-ready timeouts and graceful shutdown:
cmd/server/main.go
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "time"

    "github.com/yourorg/myapi/internal/auth"
    "github.com/yourorg/myapi/internal/config"
    "github.com/yourorg/myapi/internal/handlers"
    "github.com/joho/godotenv"
)

func main() {
    // Load environment variables from .env file
    if err := godotenv.Load(); err != nil {
        log.Println("No .env file found, using environment variables")
    }

    // Load Auth0 configuration
    cfg, err := config.LoadAuthConfig()
    if err != nil {
        log.Fatalf("Failed to load config: %v", err)
    }

    // Create JWT validator
    jwtValidator, err := auth.NewValidator(cfg.Domain, cfg.Audience)
    if err != nil {
        log.Fatalf("Failed to create validator: %v", err)
    }

    // Create HTTP middleware
    middleware, err := auth.NewMiddleware(jwtValidator)
    if err != nil {
        log.Fatalf("Failed to create middleware: %v", err)
    }

    // Setup routes
    mux := http.NewServeMux()
    mux.HandleFunc("/api/public", handlers.PublicHandler)
    mux.Handle("/api/private", middleware.CheckJWT(http.HandlerFunc(handlers.PrivateHandler)))
    mux.Handle("/api/private-scoped", middleware.CheckJWT(http.HandlerFunc(handlers.ScopedHandler)))

    // Configure server with production timeouts
    srv := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  60 * time.Second,
    }

    // Start server in goroutine
    go func() {
        log.Println("Server starting on :8080")
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Server failed: %v", err)
        }
    }()

    // Graceful shutdown
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)
    <-quit

    log.Println("Shutting down server...")
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("Server forced to shutdown: %v", err)
    }

    log.Println("Server exited")
}
myapi/
├── cmd/
│   └── server/
│       └── main.go              # Application entry point
├── internal/
│   ├── auth/
│   │   ├── claims.go            # Custom JWT claims
│   │   ├── middleware.go         # JWT middleware
│   │   └── validator.go         # JWT validator
│   ├── config/
│   │   └── auth.go              # Configuration loader
│   └── handlers/
│       └── api.go               # HTTP handlers (public, private, scoped)
├── .env                         # Environment variables (not committed)
├── .gitignore
├── go.mod
└── go.sum
8

Run and test your API

Start the development server:
go run cmd/server/main.go
You should see: Server starting on :8080Test the public endpoint (no authentication required):
curl http://localhost:8080/api/public
You should see:
{
  "message": "Hello from a public endpoint! You don't need to be authenticated to see this."
}
Test the private endpoint without a token (should fail):
curl http://localhost:8080/api/private
You should see a 401 Unauthorized error:
{
  "message": "Failed to validate JWT."
}
To test with a valid token, navigate to your API in the Auth0 Dashboard, click the Test tab, and copy the access token. Then run:
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
     http://localhost:8080/api/private
Test the scoped endpoint (requires read:messages permission):
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
     http://localhost:8080/api/private-scoped
CheckpointYou should now have a protected Go API. Your API:
  1. Accepts requests to public endpoints without authentication
  2. Rejects requests to protected endpoints without a valid token
  3. Validates JWT tokens against your Auth0 domain and audience
  4. Enforces permission-based access control using scopes

Calling Your API

You can call your protected API from any application by passing an access token in the Authorization header as a Bearer token.

Client code examples

curl --request GET \
  --url http://localhost:8080/api/private \
  --header 'authorization: Bearer YOUR_ACCESS_TOKEN'
If you are calling the API from a Single-Page Application or a Mobile/Native application, after the authorization flow is completed, you will get an access token. How you get the token and how you make the call to the API will depend on the type of application you are developing and the framework you are using.

Single-Page Applications

React, Vue, Angular quickstarts with examples

Mobile / Native Applications

iOS, Android, React Native quickstarts

Advanced Usage

DPoP (Demonstrating Proof-of-Possession) per RFC 9449 provides enhanced security by preventing token theft through cryptographic key binding.
internal/auth/middleware.go
func NewMiddleware(jwtValidator *validator.Validator) *jwtmiddleware.JWTMiddleware {
    return jwtmiddleware.New(
        jwtmiddleware.WithValidator(jwtValidator),
        jwtmiddleware.WithDPoPMode(jwtmiddleware.DPoPRequired),
        jwtmiddleware.WithLogger(slog.Default()),
    )
}
DPoP Modes:
  • DPoPAllowed (default) — Accept both Bearer and DPoP tokens
  • DPoPRequired — Only accept DPoP tokens, reject Bearer
  • DPoPDisabled — Only accept Bearer tokens, reject DPoP
DPoP is recommended for financial APIs, healthcare APIs, and high-security enterprise applications. Learn more in the DPoP documentation.
Enable CORS to allow requests from web applications. You can use a simple middleware or a library like rs/cors:
go get github.com/rs/cors
cmd/server/main.go
import "github.com/rs/cors"

// Wrap the mux with CORS middleware
handler := cors.New(cors.Options{
    AllowedOrigins:   []string{"http://localhost:3000"},
    AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE"},
    AllowedHeaders:   []string{"Authorization", "Content-Type"},
    AllowCredentials: true,
}).Handler(mux)

srv := &http.Server{
    Addr:    ":8080",
    Handler: handler,
}
For production, specify exact origins instead of wildcards.
Enable detailed logging for debugging token validation:
internal/auth/middleware.go
middleware := jwtmiddleware.New(
    jwtmiddleware.WithValidator(jwtValidator),
    jwtmiddleware.WithLogger(slog.Default()),
)
Add startup verification:
cmd/server/main.go
log.Printf("Validator configured:")
log.Printf("  Issuer: https://%s/", cfg.Domain)
log.Printf("  Audience: %s", cfg.Audience)
log.Printf("  Algorithm: RS256")

Troubleshooting

”Failed to validate JWT” or 401 Unauthorized

Problem: The API cannot find or validate the access token.Solutions:
  1. Ensure the Authorization header is present: Authorization: Bearer YOUR_TOKEN
  2. Check that “Bearer” is included before the token
  3. Verify the token is not expired
  4. Ensure you’re using an access token, not an ID token

”aud claim mismatch”

Problem: The token’s audience doesn’t match your API.Solution: Verify AUTH0_AUDIENCE exactly matches your API Identifier from the Auth0 Dashboard. The audience should NOT have a trailing slash:
# Correct
AUTH0_AUDIENCE=https://my-go-api.example.com

# Wrong (no trailing slash)
AUTH0_AUDIENCE=https://my-go-api.example.com/
The client application must also request a token with the correct audience parameter.

”unexpected signing method”

Problem: Token algorithm doesn’t match validator configuration.Solutions:
  1. Auth0 uses RS256 by default (asymmetric)
  2. Ensure your validator specifies validator.RS256
  3. Never use validator.HS256 for Auth0 tokens unless specifically configured

JWKS endpoint unreachable

Problem: The JWKS caching provider cannot reach Auth0’s public key endpoint.Solutions:
  1. Check network connectivity to Auth0 (firewall/proxy settings)
  2. Test JWKS endpoint manually: curl https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json
  3. Verify correct Auth0 region (us/eu/au)

Wrong import path

Problem: cannot find package "github.com/auth0/go-jwt-middleware/v3/..."Solution: Ensure all imports use the /v3 suffix:
// Correct
import "github.com/auth0/go-jwt-middleware/v3/validator"

// Wrong
import "github.com/auth0/go-jwt-middleware/validator"

Clock skew / token expired errors

Problem: Server clock is out of sync, causing valid tokens to appear expired.Solution: The validator already includes 30s clock skew tolerance. If you need more, adjust:
validator.WithAllowedClockSkew(60*time.Second)

Claims extraction fails

Problem: Failed to retrieve claims when using generics.Solution: Ensure you’re using the correct type parameter:
claims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())

Next Steps

Now that you have a protected API, consider exploring:

Resources