> ## Documentation Index
> Fetch the complete documentation index at: https://auth0-docs-event-stream-action-templates.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> This guide demonstrates how to add user login to a Go web application using the go-auth0 SDK.

# Add Login to Your Go Application

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

export const CreateInteractiveApp = ({placeholderText = 'Auth0', appType = 'regular_web', allowedCallbackUrls = ['localhost:3000'], allowedLogoutUrls = ['localhost:3000'], allowedOriginUrls = ['localhost:3000']}) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [storeReady, setStoreReady] = useState(false);
  const [displayForm, setDisplayForm] = useState(true);
  useEffect(() => {
    const init = () => setStoreReady(true);
    if (window.rootStore) {
      window.rootStore.clientStore.setSelectedClient(null);
      window.rootStore.clientStore.setSelectedClientSecret(undefined);
      init();
    } else {
      window.addEventListener('adu:storeReady', init);
    }
    return () => {
      window.removeEventListener('adu:storeReady', init);
    };
  }, []);
  useEffect(() => {
    if (!storeReady) return;
    const disposer = autorun(() => {
      const rootStore = window.rootStore;
      setIsAuthenticated(rootStore.sessionStore.isAuthenticated);
    });
    return () => {
      disposer();
    };
  }, [storeReady]);
  if (!storeReady || typeof window === 'undefined' || !displayForm) {
    return <></>;
  }
  const login = () => {
    const baseUrl = window.rootStore.config.apiBaseUrl;
    const returnTo = encodeURIComponent(window.location.href);
    window.location.href = `${baseUrl}/auth/user/login?returnTo=${returnTo}`;
  };
  const Card = ({className = '', children}) => {
    return <div className={`
          flex border rounded-2xl
          border-gray-950/10 dark:border-white/10
          py-3.5 px-4 gap-2
          text-sm text-gray-900 dark:text-gray-200
          ${className}
        `}>
        {children}
      </div>;
  };
  const Button = ({children, ...props}) => {
    return <button className="bg-[--button-primary] text-[--foreground-inverse] px-[1.125rem] py-1.5 rounded-lg font-medium" {...props}>
        {children}
      </button>;
  };
  const CreateApplicationForm = () => {
    const [name, setName] = useState('');
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState('');
    const handleSubmit = async () => {
      if (!name.trim()) {
        setError('Application name is required');
        return;
      }
      setIsLoading(true);
      setError(null);
      try {
        await window.rootStore.clientStore.createClient({
          name: name.trim(),
          app_type: appType,
          callbacks: allowedCallbackUrls,
          allowed_logout_urls: allowedLogoutUrls,
          web_origins: allowedOriginUrls,
          client_metadata: {
            created_by: 'quickstart-docs-app-creation-component'
          }
        });
        setDisplayForm(false);
      } catch (err) {
        console.error('Error creating client:', err);
        const errorMessage = err instanceof Error ? err.message : 'Failed to create application';
        setError(errorMessage);
      } finally {
        setIsLoading(false);
      }
    };
    return <Card className="flex-col items-start p-4 gap-3.75">
        <span className="font-medium text-gray-900 dark:text-gray-200">
          Create Auth0 App
        </span>
        <div className="w-full flex gap-2">
          <input id="app-name" name={name} className="
              w-full max-w-[448px] h-11 py-2 px-4 
              border rounded-lg border-gray-950/10 dark:border-white/10 
              text-gray-900 dark:text-gray-200
              focus:outline-none dark:focus:outline-none
            " placeholder={`My ${placeholderText} App`} value={name} onChange={e => setName(e.target.value)} />
          <Button onClick={handleSubmit}>
            {isLoading ? 'Creating...' : 'Create'}
          </Button>
        </div>
        {error && <p className="text-red-500">{error}</p>}
      </Card>;
  };
  const SignInForm = () => {
    return <Card className="items-center">
        <Button onClick={login}>Log in</Button> <span>to create the app</span>
      </Card>;
  };
  return isAuthenticated ? <CreateApplicationForm /> : <SignInForm />;
};

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>;
};

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

<HowToSchema />

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

  * **[Go](https://go.dev/doc/install)** 1.25 or newer
  * **[Git](https://git-scm.com/downloads)** for version control

  Verify installation: `go version`
</Note>

## Get Started

This quickstart demonstrates how to add Auth0 authentication to a Go web application. You'll build a server-side app with login, logout, and user profile features using the [go-auth0](https://github.com/auth0/go-auth0) SDK and Go's standard `net/http` library.

export const localEnvSnippet = `# 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 application's Client ID.
AUTH0_CLIENT_ID={yourClientId}

# Your Auth0 application's Client Secret.
AUTH0_CLIENT_SECRET={yourClientSecret}

# The Callback URL of your application.
AUTH0_CALLBACK_URL=http://localhost:3000/callback

# A long random secret used to encrypt session cookies.
SESSION_SECRET=a-long-random-secret-key-for-cookie-encryption`;

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

    ```shellscript theme={null}
    mkdir myapp && cd myapp
    go mod init myapp
    ```

    Install the required dependencies:

    ```shellscript theme={null}
    go get github.com/auth0/go-auth0/v2
    go get github.com/gorilla/sessions
    go get github.com/joho/godotenv
    ```

    Create the project structure:

    ```shellscript theme={null}
    mkdir templates
    touch .env auth.go handlers.go main.go templates/home.html templates/user.html
    ```

    <Accordion title="View expected go.mod">
      ```go go.mod theme={null}
      module myapp

      go 1.25

      require (
          github.com/auth0/go-auth0/v2 v2.10.0
          github.com/gorilla/sessions v1.4.0
          github.com/joho/godotenv v1.5.1
      )
      ```
    </Accordion>
  </Step>

  <Step title="Setup your Auth0 application" stepNumber={2}>
    To use Auth0 services, you need an application set up in the Auth0 Dashboard. The Auth0 application is where you configure authentication for your project.

    You need the following information from your application's **Settings** tab:

    * **Domain**
    * **Client ID**
    * **Client Secret**

    <Frame>![App Dashboard](https://cdn2.auth0.com/docs/1.14550.0/media/articles/dashboard/client_settings.png)</Frame>

    You have three options to set up your Auth0 application: use the Quick Setup tool (recommended), run a CLI command, or configure manually via the Dashboard:

    <Tabs>
      <Tab title="Quick Setup (recommended)">
        Create an Auth0 App and copy the pre-filled `.env` file with the right configuration values.

        <CreateInteractiveApp placeholderText="Go" appType="regular_web" allowedCallbackUrls={["http://localhost:3000/callback"]} allowedLogoutUrls={["http://localhost:3000"]} />

        <AuthCodeBlock children={localEnvSnippet} language="shellscript" filename=".env" />
      </Tab>

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

        <CodeGroup>
          ```shellscript Mac theme={null}
          # Install Auth0 CLI (if not already installed)
          brew tap auth0/auth0-cli && brew install auth0

          # Set up Auth0 app and generate .env file
          auth0 qs setup --app --type regular --framework vanilla-go --port 3000 --name "My Go App"
          ```

          ```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

          # Set up Auth0 app and generate .env file
          auth0 qs setup --app --type regular --framework vanilla-go --port 3000 --name "My Go App"
          ```
        </CodeGroup>

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

        <AuthCodeBlock children={localEnvSnippet} language="shellscript" filename=".env" />

        <Note>
          This command will:

          1. Check if you're authenticated (and prompt for login if needed)
          2. Create an Auth0 Regular Web Application configured for `http://localhost:3000`
          3. Generate a `.env` file with your Auth0 domain, client ID, and client secret
        </Note>
      </Tab>

      <Tab title="Dashboard">
        1. Head to the [Auth0 Dashboard](https://manage.auth0.com/dashboard/)
        2. Click on **Applications** > **Applications** > **Create Application**
        3. In the popup, enter a name for your app, select `Regular Web Applications` as the app type and click **Create**
        4. Switch to the **Settings** tab on the Application Details page
        5. Note the **Domain**, **Client ID**, and **Client Secret** values

        ### Configure Callback URLs

        A callback URL is the URL in your application where Auth0 redirects the user after they have authenticated. If not set, users will not be returned to your application after login.

        Set the **Allowed Callback URLs** to:

        ```
        http://localhost:3000/callback
        ```

        ### Configure Logout URLs

        A logout URL is the URL in your application that Auth0 can return to after the user has been logged out. If not set, users will not be able to log out from your application and will receive an error.

        Set the **Allowed Logout URLs** to:

        ```
        http://localhost:3000
        ```

        ### Create your environment file

        Create a `.env` file in your project's root directory with the values from the dashboard:

        ```shellscript .env theme={null}
        AUTH0_DOMAIN=YOUR_AUTH0_DOMAIN
        AUTH0_CLIENT_ID=YOUR_AUTH0_CLIENT_ID
        AUTH0_CLIENT_SECRET=YOUR_AUTH0_CLIENT_SECRET
        AUTH0_CALLBACK_URL=http://localhost:3000/callback
        SESSION_SECRET=a-long-random-secret-key-for-cookie-encryption
        ```

        <Warning>
          Replace `YOUR_AUTH0_DOMAIN`, `YOUR_AUTH0_CLIENT_ID`, and `YOUR_AUTH0_CLIENT_SECRET` with the actual values from your application's Settings tab.
        </Warning>
      </Tab>
    </Tabs>

    <Tip>
      Verify your `.env` file exists: `cat .env` (Mac/Linux) or `type .env` (Windows)
    </Tip>
  </Step>

  <Step title="Create the Auth0 client" stepNumber={3}>
    Create the `auth.go` file. This wraps the `go-auth0` authentication client and provides a helper to build the authorization URL.

    ```go auth.go lines theme={null}
    package main

    import (
        "context"
        "fmt"
        "net/url"
        "os"

        "github.com/auth0/go-auth0/v2/authentication"
    )

    // Authenticator wraps the go-auth0 authentication client.
    type Authenticator struct {
        *authentication.Authentication
        Domain      string
        ClientID    string
        CallbackURL string
    }

    // NewAuthenticator creates and configures a new Authenticator.
    func NewAuthenticator() (*Authenticator, error) {
        domain := os.Getenv("AUTH0_DOMAIN")
        clientID := os.Getenv("AUTH0_CLIENT_ID")
        clientSecret := os.Getenv("AUTH0_CLIENT_SECRET")
        callbackURL := os.Getenv("AUTH0_CALLBACK_URL")

        authClient, err := authentication.New(
            context.Background(),
            domain,
            authentication.WithClientID(clientID),
            authentication.WithClientSecret(clientSecret),
        )
        if err != nil {
            return nil, fmt.Errorf("failed to initialize authentication client: %w", err)
        }

        return &Authenticator{
            Authentication: authClient,
            Domain:         domain,
            ClientID:       clientID,
            CallbackURL:    callbackURL,
        }, nil
    }

    // AuthorizationURL builds the /authorize URL to redirect users
    // to Auth0's Universal Login page.
    func (a *Authenticator) AuthorizationURL(state string) string {
        u, _ := url.Parse("https://" + a.Domain + "/authorize")
        params := url.Values{
            "response_type": {"code"},
            "client_id":     {a.ClientID},
            "redirect_uri":  {a.CallbackURL},
            "scope":         {"openid profile email"},
            "state":         {state},
        }
        u.RawQuery = params.Encode()
        return u.String()
    }
    ```

    **What this does:**

    * Initializes the `go-auth0` authentication client with your tenant's domain, client ID, and client secret
    * Provides an `AuthorizationURL` helper that builds the `/authorize` redirect URL with the required OAuth2 parameters
  </Step>

  <Step title="Create route handlers" stepNumber={4}>
    Create the `handlers.go` file with handlers for login, callback, user profile, and logout.

    ```go handlers.go expandable lines theme={null}
    package main

    import (
        "crypto/rand"
        "encoding/base64"
        "encoding/gob"
        "net/http"
        "net/url"
        "os"

        "github.com/auth0/go-auth0/v2/authentication/oauth"
        "github.com/gorilla/sessions"
    )

    var store *sessions.CookieStore

    func init() {
        gob.Register(map[string]interface{}{})
    }

    func initSessionStore() {
        store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_SECRET")))
        store.Options = &sessions.Options{
            Path:     "/",
            MaxAge:   86400,
            HttpOnly: true,
            Secure:   false, // Set to true in production (requires HTTPS)
            SameSite: http.SameSiteLaxMode,
        }
    }

    // HomeHandler renders the home page or redirects to /user if already logged in.
    func HomeHandler(w http.ResponseWriter, r *http.Request) {
        session, _ := store.Get(r, "auth-session")
        if session.Values["profile"] != nil {
            http.Redirect(w, r, "/user", http.StatusSeeOther)
            return
        }
        if err := templates.ExecuteTemplate(w, "home.html", nil); err != nil {
            http.Error(w, "Internal error", http.StatusInternalServerError)
        }
    }

    // LoginHandler redirects the user to Auth0's Universal Login page.
    func LoginHandler(auth *Authenticator) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            state, err := generateRandomState()
            if err != nil {
                http.Error(w, "Internal error", http.StatusInternalServerError)
                return
            }

            session, _ := store.Get(r, "auth-session")
            session.Values["state"] = state
            if err := session.Save(r, w); err != nil {
                http.Error(w, "Internal error", http.StatusInternalServerError)
                return
            }

            http.Redirect(w, r, auth.AuthorizationURL(state), http.StatusTemporaryRedirect)
        }
    }

    // CallbackHandler handles the callback from Auth0 after authentication.
    func CallbackHandler(auth *Authenticator) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            session, _ := store.Get(r, "auth-session")

            // Verify the state parameter to prevent CSRF attacks.
            if r.URL.Query().Get("state") != session.Values["state"] {
                http.Error(w, "Invalid state parameter", http.StatusBadRequest)
                return
            }

            // Exchange the authorization code for tokens.
            tokenSet, err := auth.OAuth.LoginWithAuthCode(r.Context(), oauth.LoginWithAuthCodeRequest{
                Code:        r.URL.Query().Get("code"),
                RedirectURI: auth.CallbackURL,
            }, oauth.IDTokenValidationOptions{})
            if err != nil {
                http.Error(w, "Failed to exchange authorization code for token", http.StatusUnauthorized)
                return
            }

            // Retrieve the user's profile information.
            userInfo, err := auth.UserInfo(r.Context(), tokenSet.AccessToken)
            if err != nil {
                http.Error(w, "Failed to get user info", http.StatusInternalServerError)
                return
            }

            session.Values["access_token"] = tokenSet.AccessToken
            session.Values["profile"] = map[string]interface{}{
                "nickname": userInfo.Nickname,
                "name":     userInfo.Name,
                "picture":  userInfo.Picture,
                "email":    userInfo.Email,
            }
            if err := session.Save(r, w); err != nil {
                http.Error(w, "Internal error", http.StatusInternalServerError)
                return
            }

            http.Redirect(w, r, "/user", http.StatusTemporaryRedirect)
        }
    }

    // UserHandler displays the authenticated user's profile.
    func UserHandler(w http.ResponseWriter, r *http.Request) {
        session, _ := store.Get(r, "auth-session")
        profile, ok := session.Values["profile"].(map[string]interface{})
        if !ok {
            http.Redirect(w, r, "/", http.StatusSeeOther)
            return
        }

        if err := templates.ExecuteTemplate(w, "user.html", profile); err != nil {
            http.Error(w, "Internal error", http.StatusInternalServerError)
        }
    }

    // LogoutHandler clears the session and redirects to Auth0's logout endpoint.
    func LogoutHandler(auth *Authenticator) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            session, _ := store.Get(r, "auth-session")
            session.Options.MaxAge = -1
            session.Save(r, w)

            logoutURL, _ := url.Parse("https://" + auth.Domain + "/v2/logout")
            scheme := "http"
            if r.TLS != nil {
                scheme = "https"
            }

            returnTo, _ := url.Parse(scheme + "://" + r.Host)
            params := url.Values{}
            params.Add("returnTo", returnTo.String())
            params.Add("client_id", auth.ClientID)
            logoutURL.RawQuery = params.Encode()

            http.Redirect(w, r, logoutURL.String(), http.StatusTemporaryRedirect)
        }
    }

    func generateRandomState() (string, error) {
        b := make([]byte, 32)
        _, err := rand.Read(b)
        if err != nil {
            return "", err
        }
        return base64.RawURLEncoding.EncodeToString(b), nil
    }
    ```

    **Key points:**

    * **LoginHandler** generates a random state parameter for CSRF protection and redirects to Auth0's Universal Login
    * **CallbackHandler** exchanges the authorization code for tokens using `go-auth0`, then calls `UserInfo` to get the user's profile
    * **LogoutHandler** clears the session and redirects to Auth0's `/v2/logout` endpoint
    * **UserHandler** retrieves the profile from the session and renders the template
  </Step>

  <Step title="Create HTML templates" stepNumber={5}>
    <Tabs>
      <Tab title="home.html">
        ```html templates/home.html expandable lines theme={null}
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Auth0 Go Web App</title>
            <style>
                * { margin: 0; padding: 0; box-sizing: border-box; }
                body {
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                    background: #0a0a0a;
                    color: #e2e8f0;
                    min-height: 100vh;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                }
                .container {
                    text-align: center;
                    padding: 3rem;
                    background: #1a1a2e;
                    border-radius: 16px;
                    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
                    max-width: 440px;
                    width: 90%;
                }
                .logo {
                    width: 48px;
                    height: 48px;
                    margin: 0 auto 1.5rem;
                    background: linear-gradient(135deg, #635bff, #00d4aa);
                    border-radius: 12px;
                }
                h1 {
                    font-size: 2rem;
                    font-weight: 700;
                    margin-bottom: 0.5rem;
                    color: #f8fafc;
                }
                p {
                    color: #94a3b8;
                    font-size: 1.05rem;
                    margin-bottom: 2rem;
                    line-height: 1.6;
                }
                .btn {
                    display: inline-block;
                    padding: 0.85rem 2.5rem;
                    background: linear-gradient(135deg, #635bff, #4f46e5);
                    color: #fff;
                    text-decoration: none;
                    border-radius: 8px;
                    font-size: 1rem;
                    font-weight: 600;
                    transition: transform 0.2s, box-shadow 0.2s;
                    box-shadow: 0 4px 14px rgba(99, 91, 255, 0.4);
                }
                .btn:hover {
                    transform: translateY(-2px);
                    box-shadow: 0 6px 20px rgba(99, 91, 255, 0.6);
                }
                .footer {
                    margin-top: 2rem;
                    font-size: 0.85rem;
                    color: #475569;
                }
                .footer a { color: #635bff; text-decoration: none; }
            </style>
        </head>
        <body>
            <div class="container">
                <div class="logo"></div>
                <h1>Go + Auth0</h1>
                <p>Secure authentication for your Go web application, powered by Auth0.</p>
                <a href="/login" class="btn">Sign In</a>
                <div class="footer">
                    Protected by <a href="https://auth0.com">Auth0</a>
                </div>
            </div>
        </body>
        </html>
        ```
      </Tab>

      <Tab title="user.html">
        ```html templates/user.html expandable lines theme={null}
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Profile</title>
            <style>
                * { margin: 0; padding: 0; box-sizing: border-box; }
                body {
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                    background: #0a0a0a;
                    color: #e2e8f0;
                    min-height: 100vh;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                }
                .container {
                    text-align: center;
                    padding: 3rem;
                    background: #1a1a2e;
                    border-radius: 16px;
                    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
                    max-width: 440px;
                    width: 90%;
                }
                .avatar {
                    display: block;
                    width: 96px;
                    height: 96px;
                    border-radius: 50%;
                    border: 3px solid #635bff;
                    margin: 0 auto 1.5rem;
                    object-fit: cover;
                    background: #2d2d5e;
                }
                .avatar[src=""], .avatar:not([src]) {
                    display: none;
                }
                .avatar-fallback {
                    width: 96px;
                    height: 96px;
                    border-radius: 50%;
                    border: 3px solid #635bff;
                    margin: 0 auto 1.5rem;
                    background: linear-gradient(135deg, #635bff, #4f46e5);
                    display: none;
                    align-items: center;
                    justify-content: center;
                    font-size: 2rem;
                    font-weight: 700;
                    color: #fff;
                }
                .badge {
                    display: inline-block;
                    padding: 0.3rem 0.75rem;
                    background: rgba(0, 212, 170, 0.15);
                    color: #00d4aa;
                    border-radius: 20px;
                    font-size: 0.8rem;
                    font-weight: 600;
                    margin-bottom: 1rem;
                }
                h2 {
                    font-size: 1.75rem;
                    font-weight: 700;
                    color: #f8fafc;
                    margin-bottom: 0.25rem;
                }
                .email {
                    color: #94a3b8;
                    font-size: 1rem;
                    margin-bottom: 2rem;
                }
                .info-card {
                    background: #0f0f23;
                    border-radius: 10px;
                    padding: 1.25rem;
                    margin-bottom: 1.5rem;
                    text-align: left;
                }
                .info-row {
                    display: flex;
                    justify-content: space-between;
                    padding: 0.5rem 0;
                    border-bottom: 1px solid #1e293b;
                }
                .info-row:last-child { border-bottom: none; }
                .info-label { color: #64748b; font-size: 0.875rem; }
                .info-value { color: #e2e8f0; font-size: 0.875rem; font-weight: 500; }
                .btn-logout {
                    display: inline-block;
                    padding: 0.75rem 2rem;
                    background: transparent;
                    color: #f87171;
                    text-decoration: none;
                    border-radius: 8px;
                    font-size: 0.95rem;
                    font-weight: 600;
                    border: 1px solid #f87171;
                    transition: all 0.2s;
                }
                .btn-logout:hover {
                    background: #f87171;
                    color: #0a0a0a;
                }
            </style>
        </head>
        <body>
            <div class="container">
                <img class="avatar" src="{{ .picture }}" alt="{{ .name }}" onerror="this.style.display='none';document.getElementById('avatar-fallback').style.display='flex';" />
                <div id="avatar-fallback" class="avatar-fallback">{{ if .nickname }}{{ slice .nickname 0 1 }}{{ else }}?{{ end }}</div>
                <span class="badge">Authenticated</span>
                <h2>{{ .nickname }}</h2>
                <p class="email">{{ .email }}</p>
                <div class="info-card">
                    <div class="info-row">
                        <span class="info-label">Full Name</span>
                        <span class="info-value">{{ .name }}</span>
                    </div>
                    <div class="info-row">
                        <span class="info-label">Nickname</span>
                        <span class="info-value">{{ .nickname }}</span>
                    </div>
                    <div class="info-row">
                        <span class="info-label">Email</span>
                        <span class="info-value">{{ .email }}</span>
                    </div>
                </div>
                <a href="/logout" class="btn-logout">Sign Out</a>
            </div>
        </body>
        </html>
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Create the main server" stepNumber={6}>
    Wire everything together in `main.go`:

    ```go main.go lines theme={null}
    package main

    import (
        "html/template"
        "log"
        "net/http"

        "github.com/joho/godotenv"
    )

    var templates *template.Template

    func main() {
        // .env file is optional; in Docker, env vars come from --env-file flag.
        godotenv.Load()

        initSessionStore()

        auth, err := NewAuthenticator()
        if err != nil {
            log.Fatalf("Failed to initialize the authenticator: %v", err)
        }

        templates = template.Must(template.ParseGlob("templates/*.html"))

        mux := http.NewServeMux()
        mux.HandleFunc("/", HomeHandler)
        mux.HandleFunc("/login", LoginHandler(auth))
        mux.HandleFunc("/callback", CallbackHandler(auth))
        mux.HandleFunc("/user", UserHandler)
        mux.HandleFunc("/logout", LogoutHandler(auth))

        log.Print("Server listening on http://localhost:3000/")
        if err := http.ListenAndServe("0.0.0.0:3000", mux); err != nil {
            log.Fatalf("There was an error with the http server: %v", err)
        }
    }
    ```

    <Accordion title="Project structure">
      ```
      myapp/
      ├── main.go          # Application entry point and routes
      ├── auth.go          # Auth0 client setup and authorization URL
      ├── handlers.go      # HTTP handlers (login, callback, logout, profile)
      ├── templates/
      │   ├── home.html    # Home page with sign-in link
      │   └── user.html    # User profile page
      ├── .env             # Environment variables (not committed)
      ├── go.mod
      └── go.sum
      ```
    </Accordion>
  </Step>

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

    ```shellscript theme={null}
    go run .
    ```

    You should see: `Server listening on http://localhost:3000/`

    Open [http://localhost:3000](http://localhost:3000) in your browser. Click **Sign In** to be redirected to Auth0's Universal Login page. After authenticating, you'll be redirected back to your app and see your profile information.

    <Info>
      If port 3000 is already in use, update the `AUTH0_CALLBACK_URL` in your `.env` file and the **Allowed Callback URLs** and **Allowed Logout URLs** in your Auth0 Application Settings to use the new port.
    </Info>
  </Step>
</Steps>

<Check>
  **Checkpoint**

  You should now have a fully functional Go web application with Auth0 authentication running on your [localhost](http://localhost:3000/). Your app:

  1. Redirects users to Auth0's Universal Login for authentication
  2. Exchanges the authorization code for tokens using the `go-auth0` SDK
  3. Retrieves and displays user profile information
  4. Supports logout with session cleanup
</Check>

***

## Advanced Usage

<Accordion title="Protecting routes with middleware">
  Create an `IsAuthenticated` middleware to protect routes that require authentication. Add this to your `handlers.go`:

  ```go handlers.go theme={null}
  // IsAuthenticated is a middleware that checks if
  // the user has been authenticated.
  func IsAuthenticated(next http.Handler) http.Handler {
      return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
          session, _ := store.Get(r, "auth-session")
          if session.Values["profile"] == nil {
              http.Redirect(w, r, "/", http.StatusSeeOther)
              return
          }
          next.ServeHTTP(w, r)
      })
  }
  ```

  Apply the middleware to protected routes in `main.go`:

  ```go main.go theme={null}
  mux.Handle("/user", IsAuthenticated(http.HandlerFunc(UserHandler)))
  ```
</Accordion>

<Accordion title="Calling a protected API">
  After authentication, the access token stored in the session can be used to call a protected API:

  ```go theme={null}
  func ApiHandler(w http.ResponseWriter, r *http.Request) {
      session, _ := store.Get(r, "auth-session")
      accessToken, ok := session.Values["access_token"].(string)
      if !ok {
          http.Error(w, "Unauthorized", http.StatusUnauthorized)
          return
      }

      req, _ := http.NewRequest("GET", "https://your-api.example.com/api/private", nil)
      req.Header.Add("Authorization", "Bearer "+accessToken)

      res, err := http.DefaultClient.Do(req)
      if err != nil {
          http.Error(w, "API request failed", http.StatusInternalServerError)
          return
      }
      defer res.Body.Close()

      w.Header().Set("Content-Type", "application/json")
      io.Copy(w, res.Body)
  }
  ```

  <Note>
    To request an access token scoped to your API, add the `audience` parameter to the authorization URL in `auth.go`:

    ```go theme={null}
    params := url.Values{
        "response_type": {"code"},
        "client_id":     {a.ClientID},
        "redirect_uri":  {a.CallbackURL},
        "scope":         {"openid profile email"},
        "audience":      {"https://your-api.example.com"},
        "state":         {state},
    }
    ```
  </Note>
</Accordion>

<Accordion title="Refreshing tokens">
  If your application uses refresh tokens, you can exchange a refresh token for a new set of tokens using the `go-auth0` SDK:

  ```go theme={null}
  newTokenSet, err := auth.OAuth.RefreshToken(r.Context(), oauth.RefreshTokenRequest{
      RefreshToken: storedRefreshToken,
  })
  ```

  To receive a refresh token, add `offline_access` to the scopes in your authorization URL:

  ```go theme={null}
  "scope": {"openid profile email offline_access"},
  ```
</Accordion>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Common issues and solutions">
    ### "Failed to exchange authorization code for token"

    **Problem:** The callback handler cannot exchange the authorization code.

    **Solutions:**

    1. Verify `AUTH0_CLIENT_SECRET` is correct in your `.env` file
    2. Ensure the `AUTH0_CALLBACK_URL` exactly matches the **Allowed Callback URLs** in your Auth0 Application Settings
    3. Check that the authorization code hasn't expired (codes are single-use and short-lived)

    ### "Failed to get user info"

    **Problem:** The `/userinfo` endpoint returns an error.

    **Solutions:**

    1. Ensure the `openid` scope is included in the authorization URL
    2. Verify the access token is valid and not expired
    3. Check network connectivity to your Auth0 domain

    ### "Invalid state parameter"

    **Problem:** The state parameter in the callback doesn't match the session.

    **Solutions:**

    1. Ensure cookies are enabled in your browser
    2. Check that the session store secret hasn't changed between requests
    3. Verify you're not using multiple browser tabs during the login flow

    ### Users can't log out

    **Problem:** After clicking logout, users see an Auth0 error page.

    **Solutions:**

    1. Verify `http://localhost:3000` is in the **Allowed Logout URLs** in your Auth0 Application Settings
    2. Ensure the `client_id` parameter matches your application's Client ID
    3. Check that the `returnTo` URL exactly matches one of the allowed logout URLs

    ### Session data not persisting

    **Problem:** User profile data disappears between requests.

    **Solutions:**

    1. Ensure `gob.Register(map[string]interface{}{})` is called before storing data
    2. Check that `session.Save(r, w)` is called after modifying session values
    3. Verify cookies are not being blocked by browser settings
  </Accordion>
</AccordionGroup>

***

## Next Steps

Now that you have authentication working, consider exploring:

* **[User Profiles](/docs/manage-users/user-accounts/user-profiles)** — Learn about the user information available after authentication
* **[Configure other identity providers](/docs/authenticate/identity-providers)** — Add social or enterprise login options
* **[Enable multifactor authentication](/docs/secure/multi-factor-authentication)** — Add an extra layer of security
* **[Access Token Best Practices](/docs/secure/tokens/access-tokens)** — Learn about token security
* **[Production Checklist](/docs/deploy-monitor/pre-deployment-checks/how-to-run-production-checks)** — Pre-launch security review

***

## Resources

* **[go-auth0 GitHub](https://github.com/auth0/go-auth0)** — Source code and documentation
* **[Go Web App Sample](https://github.com/auth0-samples/auth0-golang-web-app/tree/master/01-Login-Quickstart)** — Complete working example
* **[Auth0 Community](https://community.auth0.com/)** — Get help from the community
