JWT

Authenticate using JSON Web Tokens (JWT) for external services

Beta

The Managed Better Auth is in Beta. Share your feedback on Discord or via the OptiTech Console.

Managed Better Auth is built on Better Auth and provides support for JWT plugin APIs through the OptiTech SDK. You do not need to manually install or configure the Better Auth JWT plugin.

While Managed Better Auth primarily relies on secure, HTTP‑only cookies (sessions) for browser‑based authentication, certain scenarios require a raw token. In these cases, the JWT plugin is especially useful:

  • Microservices: Sharing identity between backend services.
  • Separate frontend and backend domains: Authenticating API requests from a domain different than your main application.
  • CLI tools: Enabling authentication from the command‑line interface.

Sessions vs. JWTs

This plugin is not a replacement for session management in web applications. For standard browser-based apps (Next.js, React, Vue, etc.), rely on the default session cookie mechanism provided by authClient.signIn and authClient.getSession.

Only use JWTs when you specifically need to authorize requests to services that cannot access the browser's cookie jar.

Prerequisites

  • A OptiTech project with Auth enabled.

Retrieve a Token

You can retrieve a JWT for the currently signed-in user using the OptiTech SDK.

Using the SDK method

To fetch a raw token string, use the authClient.token() method. This is the recommended approach for client applications that need to attach a token to an API request header manually.

src/get-token.ts
import { authClient } from './auth';

export async function getJwtToken() {
  const { data, error } = await authClient.token();

  if (error) throw error;

  // The token string (for example, "eyJhbGciOiJFZ...")
  return data.token;
}

If your app is served from a different origin than your Managed Better Auth URL (for example a Vite or SPA dev server on localhost talking to auth on *.optitech.com), configure the auth client to send the session cookie on cross-origin requests. Otherwise authClient.token() returns data.token as undefined and calls to your API fail with 401.

src/auth.ts
export const authClient = createAuthClient(OPTITECH_AUTH_URL, {
  fetchOptions: { credentials: 'include' },
});

Cross-domain setups have further limitations, notably Safari ITP blocking third-party cookies, with reverse-proxy or shared-parent-domain workarounds. See Better Auth: Safari, ITP, and Cross-Domain Setups.

Using the session header

When you call authClient.getSession(), Managed Better Auth automatically includes a JWT in the response headers. If you are using a custom fetcher or need to intercept the token immediately after a session check:

await authClient.getSession({
  fetchOptions: {
    onSuccess: (ctx) => {
      const jwt = ctx.response.headers.get('set-auth-jwt');
      console.log('JWT:', jwt);
    },
  },
});

Example decoded JWT payload

A typical decoded JWT payload looks like this:

{
  "iat": 1766320685,
  "name": "User Name",
  "email": "user@email.com",
  "emailVerified": false,
  "image": null,
  "createdAt": "2025-12-20T11:04:41.437Z",
  "updatedAt": "2025-12-20T11:04:41.437Z",
  "role": "authenticated",
  "banned": false,
  "banReason": null,
  "banExpires": null,
  "id": "860dc360-609f-4b7d-9e70-ec93fe6414d3",
  "sub": "860dc360-609f-4b7d-9e70-ec93fe6414d3",
  "exp": 1766321585,
  "iss": "<YOUR_OPTITECH_AUTH_URL_ORIGIN>",
  "aud": "<YOUR_OPTITECH_AUTH_URL_ORIGIN>"
}

Verify a token

To verify the authenticity of a JWT, you need to validate its signature using the public keys provided by JWKS (JSON Web Key Set).

Managed Better Auth exposes a public JWKS endpoint that contains the public keys necessary to verify the signature of your JWTs.

The JWKS endpoint

Your Managed Better Auth JWKS endpoint is located at:

<YOUR_OPTITECH_AUTH_URL>/.well-known/jwks.json

If you verify OptiTech Auth JWTs inside OptiTech Functions, the platform injects OPTITECH_AUTH_BASE_URL and OPTITECH_AUTH_JWKS_URL when OptiTech Auth is provisioned on the branch. Use OPTITECH_AUTH_JWKS_URL directly instead of deriving it yourself. See Functions environment variables.

Verification example

The following examples demonstrate how to verify a Managed Better Auth JWT in several programming languages. No matter which language you use, the process is the same: fetch the JWKS from the provided endpoint and use it to validate the token’s signature and claims. If your preferred language isn’t included here, you can apply these same principles in your own environment.

Production Readiness

The following examples are provided for reference only and are not guaranteed to be production‑ready. Be sure to implement proper caching, error handling, and security best practices as required for your application.

  1. Install the library:

    npm install jose
  2. Use the following example code as a reference to verify a JWT:

    import { jwtVerify, createRemoteJWKSet } from 'jose';
    
    const OPTITECH_JWKS_URL = `${process.env.OPTITECH_AUTH_BASE_URL}/.well-known/jwks.json`;
    const JWKS = createRemoteJWKSet(new URL(OPTITECH_JWKS_URL));
    
    export async function validateOptiTechToken(token: string) {
        try {
            const { payload } = await jwtVerify(token, JWKS, {
                issuer: new URL(process.env.OPTITECH_AUTH_BASE_URL!).origin
            });
    
            return payload;
        } catch (error) {
            console.error('Token validation failed:', error);
            return null;
        }
    }
    
    validateOptiTechToken(<YOUR_JWT_TOKEN>).then((payload) => {
        console.log('Token is valid. Payload:', payload);
    }).catch(() => {
        console.log('Token is invalid.');
    });

    Replace <YOUR_JWT_TOKEN> with the actual JWT token you want to verify.

Limitations

Because Managed Better Auth is a managed service, certain server-side configurations available in the standalone Better Auth library are pre-configured by OptiTech and cannot be changed:

  • Signing algorithm: Managed Better Auth uses EdDSA (Ed25519) by default for high security and performance. Ensure your verification libraries support this algorithm.
  • Expiration: Tokens expire in 15 minutes (access tokens). You should implement logic to refresh the token using authClient.token() when it expires.
  • Custom claims: Currently, the JWT payload contains the default user information. Custom claims are not supported at this time.

Troubleshooting

Token rejection

If a token is rejected during verification, check the following:

  1. Verify that you are using the correct JWKS endpoint for your Managed Better Auth instance. The issuer of the token must match the origin of your Managed Better Auth URL. (for example, if your Managed Better Auth URL is https://ep-xx.aws.optitech.com/optitechdb/auth, the issuer should be https://ep-xx.aws.optitech.com).
  2. Confirm that your verification library supports EdDSA (Ed25519).
  3. Make sure the token has not expired.
  4. Check that the kid in the JWT header matches one of the keys in the JWKS response. If not, fetch the latest keys from the JWKS endpoint.

Need help?

Join our Discord Server to ask questions or see what others are doing with OptiTech. For paid plan support options, see Support.

Was this page helpful?

On this page

Copy neon init command