Skip to main content
View On GitHub
This guide demonstrates how to integrate Auth0 with any new or existing Go API application using go-jwt-middleware.Each Auth0 API uses the API Identifier, which your application needs to validate the access token.
New to Auth0? Learn how Auth0 works and read about implementing API authentication and authorization using the OAuth 2.0 framework.
1
Before we begin, make sure you have the following:

Go 1.24+

Required for v3 of go-jwt-middleware (which uses generics). Check your version with go version

Auth0 Account

Sign up for free at auth0.com/signup

Code Editor

VS Code, GoLand, or your preferred editor

Terminal Access

Command line for running Go commands
New to Go? This tutorial assumes basic familiarity with Go syntax and HTTP servers. Check out Go by Example if you need a refresher.
2
Create an API in your Auth0 Dashboard to get started.
1

Navigate to APIs

Go to the APIs section in the Auth0 dashboard and click Create API.
Create API
2

Configure API settings

  • Name: Quickstarts (or any descriptive name)
  • Identifier: https://quickstarts/api (this becomes your audience)
  • Signing Algorithm: Leave as RS256 (recommended)
The API Identifier is a logical identifier - it doesn’t need to be a real URL.
3

Understand RS256

Your API uses RS256 (asymmetric algorithm):
  • Auth0 signs tokens with a private key
  • Your API verifies tokens with a public key (from JWKS)
  • Public keys are fetched from: https://{yourDomain}/.well-known/jwks.json
3
Permissions let you define how resources can be accessed on behalf of the user with a given access token.In your API settings, click the Permissions tab and create the following permission:
PermissionDescription
read:messagesRead messages from the API
Configure Permissions
This tutorial uses the read:messages scope to protect the scoped endpoint.
4
Set up your Go project structure and install the required dependencies.
1

Create project directory

mkdir myapi && cd myapi
2

Initialize Go module

go mod init github.com/yourorg/myapi
3

Install dependencies

go get github.com/auth0/go-jwt-middleware/v3
go get github.com/joho/godotenv
4

Download all dependencies

go mod download
// go.mod
module github.com/yourorg/myapi

go 1.24

require (
    github.com/auth0/go-jwt-middleware/v3 v3.0.0
    github.com/joho/godotenv v1.5.1
)
Go 1.24+ Required: This implementation uses generics for type-safe claims handling. Verify your version with go version.
5
Create a .env file in your project root to store Auth0 configuration:
# The URL of your Auth0 Tenant Domain.
# If you're using a Custom Domain, set this to that value instead.
AUTH0_DOMAIN='{yourDomain}'

# Your Auth0 API's Identifier
# Example: https://quickstarts/api
AUTH0_AUDIENCE='{yourApiIdentifier}'
Create a config package to load and validate these environment variables in internal/config/auth.go (see the code in the right sidebar).
Benefits of centralized configuration:
  • Single source of truth for environment variables
  • Clear error messages for missing configuration
  • Type-safe configuration access
  • Easy to test and mock
  • Never commit .env files to version control
  • Add .env to your .gitignore file
  • Use different .env files for different environments
  • Validate all required config on startup (fail fast)
6
Create custom claims to extract and validate application-specific data from JWTs in internal/auth/claims.go (see the code in the right sidebar).
Custom claims allow you to:
  • Extract permissions (scopes) from JWT tokens
  • Validate claim formatting automatically
  • Add domain-specific authorization logic
  • Keep business logic separate from validation
Automatic Validation: The Validate method is called automatically by the middleware after parsing the JWT.
The Validate method ensures:
  • Scope is optional - allows tokens without scopes (for /api/private endpoint)
  • Format validation - when scope exists, checks for proper formatting
  • No whitespace issues - prevents malformed scope strings
  • Called automatically - runs before your handlers execute
The HasScope helper method:
  • Parses space-separated scopes (e.g., "read:messages write:messages")
  • Returns false if scope is empty (prevents false positives)
  • Used in handlers to enforce permission-based access
7
Create the JWT validator in internal/auth/validator.go (see the code in the right sidebar).
The validator performs these security checks on every JWT:
  • Signature verification using Auth0’s public keys (JWKS)
  • Issuer validation - iss claim matches your Auth0 domain
  • Audience validation - aud claim matches your API identifier
  • Expiration check - token hasn’t expired (exp claim)
  • Time validity - token is currently valid (nbf and iat claims)
Options Pattern: Uses functional options like validator.WithAllowedClockSkew() for flexible configurationJWKS Caching: Automatically fetches and caches Auth0’s public keys every 5 minutes, reducing network callsAlgorithm Specification: Explicitly sets RS256 to prevent algorithm confusion attacksClock Skew Tolerance: Allows 30 seconds for distributed system clock differences
The core-adapter architecture separates JWT validation logic (core) from HTTP concerns (adapter). This means:
  • You can reuse the same validator across different frameworks
  • Easier to test validation logic in isolation
  • Can use with HTTP, gRPC, or any other transport
8
Create the HTTP middleware that wraps your validator in internal/auth/middleware.go (see the code in the right sidebar).Built-in structured logging using Go’s standard log/slog package. The middleware will log token validation events for debugging.The middleware:
  • Extracts JWT from Authorization: Bearer <token> header
  • Validates token using the core validator
  • Skips validation for OPTIONS requests (CORS preflight)
  • Injects validated claims into request context
  • Returns RFC 6750 compliant error responses on failure
9
Create API handlers with different protection levels in internal/handlers/api.go and the main server in cmd/server/main.go (see the code in the right sidebar).Generic Claims Feature:
  • GetClaims[*validator.ValidatedClaims] uses generics for compile-time type safety
  • No runtime type assertions that can panic
  • IDE autocomplete for claim fields
  • Custom claims implement Validate(ctx context.Context) error for domain-specific validation
Endpoint Protection Levels:
  1. Public (/api/public): No authentication required
  2. Private (/api/private): Valid JWT required
  3. Scoped (/api/private-scoped): Valid JWT + specific scope (read:messages) required
Production Features:
  • Graceful shutdown handling
  • Configurable timeouts (read, write, idle)
  • Proper error handling
  • Environment variable configuration
10
Now let’s test your implementation to make sure everything works correctly.
1

Start your server

go run cmd/server/main.go
You should see: Server starting on :8080
The server automatically loads environment variables from your .env file using godotenv.
2

Get a test token

  1. Go to your API in the Auth0 Dashboard
  2. Click the Test tab
  3. Click Copy Token to get a valid JWT
3

Test public endpoint

This endpoint should work without authentication:
curl http://localhost:8080/api/public
Expected: 200 OK with message “Hello from a public endpoint!”
4

Test private endpoint (without token)

This should fail with a 401 error:
curl http://localhost:8080/api/private
Expected: 401 Unauthorized {"error": "invalid_token"}
5

Test private endpoint (with token)

This should succeed with a valid token:
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
     http://localhost:8080/api/private
Expected: 200 OK with user information
6

Test scoped endpoint

This requires the read:messages scope:
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
     http://localhost:8080/api/private-scoped
Expected: 200 OK if token has read:messages scope, 403 Forbidden otherwise
Checkpoint Complete! If all tests passed, your Auth0 integration is working correctly. Your API now:
  • Validates JWTs from Auth0
  • Protects private endpoints
  • Enforces permission-based access

Continue the Tutorial

Quick Troubleshooting

Error Message:
{"error": "invalid_token", "description": "aud claim mismatch"}
Solution: Verify AUTH0_AUDIENCE exactly matches your API Identifier from the Auth0 Dashboard.
# Correct
AUTH0_AUDIENCE=https://quickstarts/api

# Wrong (no trailing slash should be added)
AUTH0_AUDIENCE=https://quickstarts/api/
Error Message:
error fetching keys: connection refused
Solutions:
  • Check network connectivity to Auth0 (firewall/proxy settings)
  • Test JWKS endpoint manually:
    curl https://your-tenant.us.auth0.com/.well-known/jwks.json
    
  • Verify correct Auth0 region (us/eu/au)
Error Message:
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"
Error Message:
{"error": "invalid_token", "description": "token is expired"}
Solutions:
  • Get a new token from the Auth0 Dashboard Test tab
  • Check if your server clock is synchronized
  • Adjust clock skew tolerance:
    validator.WithAllowedClockSkew(60*time.Second)
    
DPoP (Demonstrating Proof-of-Possession) per RFC 9449 provides enhanced security by preventing token theft through cryptographic key binding.When to use DPoP:
  • Financial APIs handling sensitive transactions
  • Healthcare APIs with patient data
  • High-security enterprise applications
Enable DPoP:
// 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
Learn more in the DPoP documentation.

Best Practices

Security:
  • Always use HTTPS in production
  • Validate algorithms explicitly (prevents algorithm confusion attacks)
  • Use short-lived access tokens (15-30 minutes) with refresh tokens
  • Enable DPoP for sensitive APIs
  • Never log tokens in plaintext
Performance:
  • Reuse validator instances (create once at startup)
  • Configure JWKS cache appropriately (5-15 minute TTL)
  • Set reasonable HTTP timeouts
  • Use connection pooling (enabled by default in Go)
Maintainability:
  • Use environment variables for credentials
  • Define type-safe custom claims structs
  • Enable structured logging with slog
  • Test with mock validators
  • Document custom claims with JSON tags

Project Structure

Recommended structure for production Go APIs:
myapi/
├── cmd/
│   └── server/
│       └── main.go              # Application entry point
├── internal/
│   ├── auth/
│   │   ├── claims.go            # Custom claims definition
│   │   ├── middleware.go        # JWT middleware setup
│   │   └── validator.go         # Validator configuration
│   ├── config/
│   │   └── auth.go              # Environment configuration
│   └── handlers/
│       └── api.go               # HTTP handlers
├── .env                         # Environment variables (not committed)
├── .gitignore
├── go.mod
├── go.sum
└── README.md

Next Steps

Now that you have a working Go API with Auth0 authentication, explore advanced features: