> ## Documentation Index
> Fetch the complete documentation index at: https://auth0.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Protect Your Go API

> This guide demonstrates how to protect Go API endpoints using JWT access tokens with the go-jwt-middleware v3 SDK.

export const HowToSchema = () => <script type="application/ld+json">
    {'{"@context":"https://schema.org","@type":"HowTo"}'}
  </script>;

export const AuthCodeBlock = ({filename, icon, language, highlight, children}) => {
  const [displayText, setDisplayText] = useState(children);
  const [copyText, setCopyText] = useState(children);
  const wrapperRef = React.useRef(null);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      if (!window.autorun || !window.rootStore) {
        return;
      }
      unsubscribe = window.autorun(() => {
        let processedChildrenForDisplay = children;
        let processedChildrenForCopy = children;
        for (const [key, value] of window.rootStore.variableStore.values.entries()) {
          const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
          let displayValue = value;
          if (key === "{yourClientSecret}" && value !== "{yourClientSecret}") {
            displayValue = value.substring(0, 3) + "*****MASKED*****";
          }
          processedChildrenForDisplay = processedChildrenForDisplay.replaceAll(new RegExp(escapedKey, "g"), displayValue);
          processedChildrenForCopy = processedChildrenForCopy.replaceAll(new RegExp(escapedKey, "g"), value);
        }
        setDisplayText(processedChildrenForDisplay);
        setCopyText(processedChildrenForCopy);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  useEffect(() => {
    if (!wrapperRef.current) return;
    const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
    let isOverriding = false;
    const handleClick = e => {
      const button = e.target.closest('[data-testid="copy-code-button"]');
      if (!button || !wrapperRef.current.contains(button)) return;
      isOverriding = true;
      navigator.clipboard.writeText = text => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
          return originalWriteText(copyText);
        }
        return originalWriteText(text);
      };
      setTimeout(() => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
        }
      }, 100);
    };
    const wrapper = wrapperRef.current;
    wrapper.addEventListener('click', handleClick, true);
    return () => {
      wrapper.removeEventListener('click', handleClick, true);
      if (navigator.clipboard.writeText !== originalWriteText) {
        navigator.clipboard.writeText = originalWriteText;
      }
    };
  }, [copyText]);
  return <div ref={wrapperRef}>
      <CodeBlock filename={filename} icon={icon} language={language} lines highlight={highlight}>
        {displayText}
      </CodeBlock>
    </div>;
};

<HowToSchema />

export const envSnippet = `# 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 (from Step 2)
# Example: https://my-go-api.example.com
AUTH0_AUDIENCE='{yourApiIdentifier}'`;

<Accordion title="Use AI to integrate Auth0" icon="microchip-ai" iconType="solid" defaultOpen>
  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](https://agentskills.io/home).

  **Install:**

  ```bash theme={null}
  npx skills add auth0/agent-skills --skill auth0-quickstart --skill go-jwt-middleware
  ```

  **Then ask your AI assistant:**

  ```text theme={null}
  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 →](/docs/quickstart/agent-skills)
</Accordion>

<Note>
  **Prerequisites:** Before you begin, ensure you have the following installed:

  * **[Go](https://go.dev/doc/install)** 1.24 or newer (required for generics support in go-jwt-middleware v3)
  * **[Git](https://git-scm.com/downloads)** for version control

  Verify installation: `go version`
</Note>

## 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](https://github.com/auth0/go-jwt-middleware) with Go's standard `net/http` library.

<Card title="View Sample on GitHub" href="https://github.com/auth0-samples/auth0-golang-api-samples/tree/master/01-Quickstart-Go-API" icon="github">
  Complete working example with tests
</Card>

<Steps>
  <Step title="Create a new project" stepNumber={1}>
    Create a new directory for your Go API and initialize a module.

    ```shellscript theme={null}
    mkdir myapi && cd myapi
    go mod init github.com/yourorg/myapi
    ```

    Install the required dependencies:

    ```shellscript theme={null}
    go get github.com/auth0/go-jwt-middleware/v3
    go get github.com/joho/godotenv
    go mod download
    ```

    Create the project structure:

    ```shellscript theme={null}
    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
    ```

    <Accordion title="View expected go.mod">
      ```go go.mod theme={null}
      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
      )
      ```
    </Accordion>
  </Step>

  <Step title="Setup your Auth0 API" stepNumber={2}>
    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:

    <Tabs>
      <Tab title="CLI">
        Run the following command in your project's root directory to create an Auth0 API:

        <CodeGroup>
          ```shellscript Mac theme={null}
          # 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
          ```

          ```powershell Windows theme={null}
          # Install Auth0 CLI (if not already installed)
          scoop bucket add auth0 https://github.com/auth0/scoop-auth0-cli.git
          scoop install auth0

          # Create Auth0 API
          auth0 apis create `
            --name "My Go API" `
            --identifier https://my-go-api.example.com
          ```
        </CodeGroup>

        After creation, copy the **Identifier** and your **Domain** values, then create your `.env` file:

        <AuthCodeBlock children={envSnippet} language="bash" />

        <Note>
          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
        </Note>
      </Tab>

      <Tab title="Dashboard">
        1. Go to the [Auth0 Dashboard](https://manage.auth0.com/dashboard/)
        2. Navigate to **Applications** → **APIs** → **Create API**
        3. Enter a name for your API (e.g., "My Go API")
        4. Set the **Identifier** (e.g., `https://my-go-api.example.com`)
           * This is your API audience and must be a valid URL format
           * It doesn't need to be a real URL, it's just an identifier
        5. Keep **Signing Algorithm** as **RS256**
        6. Click **Create**
        7. Copy the **Identifier** value from the **Settings** tab

        Create your `.env` file with the following values:

        ```bash .env theme={null}
        AUTH0_DOMAIN=YOUR_AUTH0_DOMAIN
        AUTH0_AUDIENCE=YOUR_API_IDENTIFIER
        ```

        <Warning>
          Replace `YOUR_AUTH0_DOMAIN` with your Auth0 tenant domain (e.g., `dev-abc123.us.auth0.com`) and `YOUR_API_IDENTIFIER` with your API identifier from the dashboard (e.g., `https://my-go-api.example.com`).
        </Warning>
      </Tab>
    </Tabs>

    <Tip>
      **Security**: Never commit `.env` files to version control. Add `.env` to your `.gitignore` file.
    </Tip>
  </Step>

  <Step title="Define API permissions" stepNumber={3}>
    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:

    | Permission      | Description                |
    | --------------- | -------------------------- |
    | `read:messages` | Read messages from the API |

    <Info>
      This tutorial uses the `read:messages` scope to protect the scoped endpoint. You can define additional permissions based on your application's needs.
    </Info>
  </Step>

  <Step title="Create configuration loader" stepNumber={4}>
    Create a config package to load and validate environment variables.

    ```go internal/config/auth.go lines theme={null}
    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
  </Step>

  <Step title="Create custom claims and JWT validator" stepNumber={5}>
    Custom claims allow you to extract and validate application-specific data from JWTs. The validator is the core component that verifies tokens against Auth0.

    <Tabs>
      <Tab title="claims.go">
        ```go internal/auth/claims.go lines theme={null}
        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
        }
        ```
      </Tab>

      <Tab title="validator.go">
        ```go internal/auth/validator.go lines theme={null}
        package auth

        import (
            "fmt"
            "net/url"
            "time"

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

        func NewValidator(domain, audience string) (*validator.Validator, error) {
            // Construct issuer URL (must include trailing slash)
            issuerURL, err := url.Parse("https://" + domain + "/")
            if err != nil {
                return nil, fmt.Errorf("failed to parse issuer URL: %w", err)
            }

            // Initialize JWKS provider using v3 options pattern
            provider, err := jwks.NewCachingProvider(
                jwks.WithIssuerURL(issuerURL),
                jwks.WithCacheTTL(5*time.Minute),
            )
            if err != nil {
                return nil, fmt.Errorf("failed to create JWKS provider: %w", err)
            }

            // Create validator using v3 options pattern
            jwtValidator, err := validator.New(
                validator.WithKeyFunc(provider.KeyFunc),
                validator.WithAlgorithm(validator.RS256),
                validator.WithIssuer(issuerURL.String()),
                validator.WithAudience(audience),
                validator.WithCustomClaims(func() validator.CustomClaims {
                    return &CustomClaims{}
                }),
                validator.WithAllowedClockSkew(30*time.Second),
            )
            if err != nil {
                return nil, fmt.Errorf("failed to create validator: %w", err)
            }

            return jwtValidator, nil
        }
        ```
      </Tab>
    </Tabs>

    **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](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/)
  </Step>

  <Step title="Create HTTP middleware and handlers" stepNumber={6}>
    The middleware wraps the validator for HTTP requests. The handlers demonstrate three protection levels: public, private, and permission-scoped.

    <Tabs>
      <Tab title="middleware.go">
        ```go internal/auth/middleware.go lines theme={null}
        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."}`))
                }),
            )
        }
        ```
      </Tab>

      <Tab title="api.go">
        ```go internal/handlers/api.go expandable lines theme={null}
        package handlers

        import (
            "encoding/json"
            "net/http"

            "github.com/yourorg/myapi/internal/auth"
            jwtmiddleware "github.com/auth0/go-jwt-middleware/v3"
            "github.com/auth0/go-jwt-middleware/v3/validator"
        )

        // PublicHandler - no authentication required
        func PublicHandler(w http.ResponseWriter, r *http.Request) {
            response := map[string]string{
                "message": "Hello from a public endpoint! You don't need to be authenticated to see this.",
            }
            w.Header().Set("Content-Type", "application/json")
            json.NewEncoder(w).Encode(response)
        }

        // PrivateHandler - requires valid JWT
        func PrivateHandler(w http.ResponseWriter, r *http.Request) {
            response := map[string]string{
                "message": "Hello from a private endpoint! You need to be authenticated to see this.",
            }
            w.Header().Set("Content-Type", "application/json")
            json.NewEncoder(w).Encode(response)
        }

        // ScopedHandler - requires 'read:messages' permission
        func ScopedHandler(w http.ResponseWriter, r *http.Request) {
            claims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())
            if err != nil {
                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(http.StatusUnauthorized)
                w.Write([]byte(`{"message":"Unauthorized."}`))
                return
            }

            customClaims, ok := claims.CustomClaims.(*auth.CustomClaims)
            if !ok || !customClaims.HasScope("read:messages") {
                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(http.StatusForbidden)
                w.Write([]byte(`{"message":"Insufficient scope."}`))
                return
            }

            response := map[string]string{
                "message": "Hello from a private endpoint! You need to be authenticated and have a scope of read:messages to see this.",
            }
            w.Header().Set("Content-Type", "application/json")
            json.NewEncoder(w).Encode(response)
        }
        ```
      </Tab>
    </Tabs>

    **Protection levels:**

    * **Public** (`/api/public`) — No authentication required
    * **Private** (`/api/private`) — Valid JWT required
    * **Scoped** (`/api/private-scoped`) — Valid JWT + `read:messages` permission required
  </Step>

  <Step title="Create the main server" stepNumber={7}>
    Wire everything together in the main entry point with production-ready timeouts and graceful shutdown:

    ```go cmd/server/main.go expandable lines theme={null}
    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")
    }
    ```

    <Accordion title="Project structure">
      ```
      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
      ```
    </Accordion>
  </Step>

  <Step title="Run and test your API" stepNumber={8}>
    Start the development server:

    ```shellscript theme={null}
    go run cmd/server/main.go
    ```

    You should see: `Server starting on :8080`

    Test the public endpoint (no authentication required):

    ```bash theme={null}
    curl http://localhost:8080/api/public
    ```

    You should see:

    ```json theme={null}
    {
      "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):

    ```bash theme={null}
    curl http://localhost:8080/api/private
    ```

    You should see a 401 Unauthorized error:

    ```json theme={null}
    {
      "message": "Failed to validate JWT."
    }
    ```

    To test with a valid token, navigate to your API in the [Auth0 Dashboard](https://manage.auth0.com/#/apis), click the **Test** tab, and copy the access token. Then run:

    ```bash theme={null}
    curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
         http://localhost:8080/api/private
    ```

    Test the scoped endpoint (requires `read:messages` permission):

    ```bash theme={null}
    curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
         http://localhost:8080/api/private-scoped
    ```
  </Step>
</Steps>

<Check>
  **Checkpoint**

  You 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
</Check>

***

## 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.

<Accordion title="Client code examples" defaultOpen>
  <CodeGroup>
    ```bash cURL theme={null}
    curl --request GET \
      --url http://localhost:8080/api/private \
      --header 'authorization: Bearer YOUR_ACCESS_TOKEN'
    ```

    ```go Go theme={null}
    package main

    import (
        "fmt"
        "net/http"
        "io"
    )

    func main() {
        url := "http://localhost:8080/api/private"

        req, _ := http.NewRequest("GET", url, nil)
        req.Header.Add("authorization", "Bearer YOUR_ACCESS_TOKEN")

        res, _ := http.DefaultClient.Do(req)
        defer res.Body.Close()

        body, _ := io.ReadAll(res.Body)

        fmt.Println(res)
        fmt.Println(string(body))
    }
    ```

    ```javascript Node.js theme={null}
    const axios = require("axios").default;

    const options = {
      method: 'GET',
      url: 'http://localhost:8080/api/private',
      headers: {authorization: 'Bearer YOUR_ACCESS_TOKEN'}
    };

    axios.request(options).then(function (response) {
      console.log(response.data);
    }).catch(function (error) {
      console.error(error);
    });
    ```

    ```python Python theme={null}
    import http.client

    conn = http.client.HTTPConnection("localhost", 8080)
    headers = { 'authorization': "Bearer YOUR_ACCESS_TOKEN" }

    conn.request("GET", "/api/private", headers=headers)

    res = conn.getresponse()
    data = res.read()

    print(data.decode("utf-8"))
    ```
  </CodeGroup>
</Accordion>

<Accordion title="Obtaining an access token">
  <Tabs>
    <Tab title="Single-Page or Mobile Apps">
      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.

      <CardGroup cols={2}>
        <Card title="Single-Page Applications" icon="browser" href="/docs/quickstart/spa">
          React, Vue, Angular quickstarts with examples
        </Card>

        <Card title="Mobile / Native Applications" icon="mobile" href="/docs/quickstart/native">
          iOS, Android, React Native quickstarts
        </Card>
      </CardGroup>
    </Tab>

    <Tab title="Machine-to-Machine (M2M)">
      If you are calling the API from a command-line tool or another service where there isn't a user entering their credentials, you need to use the [OAuth Client Credentials flow](https://auth0.com/docs/api/authentication#client-credentials).

      <Steps>
        <Step title="Register M2M Application">
          Register a [Machine to Machine Application](https://manage.auth0.com/#/applications) in your Auth0 Dashboard.
        </Step>

        <Step title="Get credentials">
          Copy your **Client ID** and **Client Secret** from [Application Settings](https://auth0.com/docs/get-started/dashboard/application-settings).
        </Step>

        <Step title="Request access token">
          Use the Client Credentials flow to obtain an access token:

          ```bash theme={null}
          curl --request POST \
            --url 'https://YOUR_AUTH0_DOMAIN/oauth/token' \
            --header 'content-type: application/x-www-form-urlencoded' \
            --data grant_type=client_credentials \
            --data 'client_id=YOUR_CLIENT_ID' \
            --data client_secret=YOUR_CLIENT_SECRET \
            --data audience=YOUR_API_IDENTIFIER
          ```
        </Step>
      </Steps>

      <Warning>
        **Token Reuse**: Auth0 customers are billed based on the number of Machine to Machine access tokens issued. Once your application gets an access token, it should keep using it until it expires to minimize the number of tokens requested.
      </Warning>
    </Tab>
  </Tabs>
</Accordion>

***

## Advanced Usage

<Accordion title="DPoP (Proof-of-Possession) Security">
  **DPoP (Demonstrating Proof-of-Possession)** per RFC 9449 provides enhanced security by preventing token theft through cryptographic key binding.

  ```go internal/auth/middleware.go theme={null}
  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

  <Note>
    DPoP is recommended for financial APIs, healthcare APIs, and high-security enterprise applications. Learn more in the [DPoP documentation](https://github.com/auth0/go-jwt-middleware#dpop-support).
  </Note>
</Accordion>

<Accordion title="CORS Configuration">
  Enable CORS to allow requests from web applications. You can use a simple middleware or a library like [rs/cors](https://github.com/rs/cors):

  ```bash theme={null}
  go get github.com/rs/cors
  ```

  ```go cmd/server/main.go theme={null}
  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.
</Accordion>

<Accordion title="Structured Logging with slog">
  Enable detailed logging for debugging token validation:

  ```go internal/auth/middleware.go theme={null}
  middleware := jwtmiddleware.New(
      jwtmiddleware.WithValidator(jwtValidator),
      jwtmiddleware.WithLogger(slog.Default()),
  )
  ```

  Add startup verification:

  ```go cmd/server/main.go theme={null}
  log.Printf("Validator configured:")
  log.Printf("  Issuer: https://%s/", cfg.Domain)
  log.Printf("  Audience: %s", cfg.Audience)
  log.Printf("  Algorithm: RS256")
  ```
</Accordion>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Common Issues and Solutions">
    ### "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:

    ```bash theme={null}
    # 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:

    ```go theme={null}
    // 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:

    ```go theme={null}
    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:

    ```go theme={null}
    claims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())
    ```
  </Accordion>
</AccordionGroup>

***

## Next Steps

Now that you have a protected API, consider exploring:

* **[Role-Based Access Control](https://auth0.com/docs/manage-users/access-control/rbac)** — Implement fine-grained permissions
* **[Access Token Best Practices](https://auth0.com/docs/secure/tokens/access-tokens)** — Learn about token security
* **[Monitor Your API](https://auth0.com/docs/deploy-monitor/logs)** — Set up logging and monitoring
* **[Production Readiness Checks](https://auth0.com/docs/deploy-monitor/pre-deployment-checks/production-checks-best-practices)** — Pre-launch security review

***

## Resources

* **[go-jwt-middleware GitHub](https://github.com/auth0/go-jwt-middleware)** — Source code, examples, and DPoP support
* **[Go API Sample](https://github.com/auth0-samples/auth0-golang-api-samples)** — Complete working example
* **[Auth0 Community](https://community.auth0.com/)** — Get help from the community
