BETTER AUTH KEYCLOACK JWT

Current Setup

  • Frontend: Next.js App Router with Better Auth (stateless mode)
  • Authentication Provider: Keycloak (OAuth2/OIDC)
  • Goal:
    • Store Keycloak OAuth tokens (idToken, accessToken, refreshToken) in Better Auth JWT
    • Send JWT to backend API for user verification
    • Use idToken for Keycloak logout
The Problem
Better Auth does NOT expose OAuth tokens in getSession() response by default.

When using Better Auth with genericOAuth plugin:
const session = await auth.api.getSession({ headers });
console.log(session);
// Output:
// {
//   session: { id, token, expiresAt, ... },
//   user: { id, email, name, ... }
// }
// ❌ NO access to: idToken, accessToken, refreshToken

What We Need
const session = await auth.api.getSession({ headers });
// We WANT:
// {
//   session: { ... },
//   user: { ... },
//   oauth: {  // ← THIS IS MISSING
//     idToken: "eyJhbGci...",
//     accessToken: "eyJhbGci...",
//     refreshToken: "eyJhbGci...",
//     expiresAt: 1764762870596
//   }
// }


Why This Matters
  • Backend Authentication: We need to send accessToken or a derived JWT to the backend API
  • Keycloak Logout: We need idToken to properly logout from Keycloak using id_token_hint
  • Token Refresh: We need refreshToken to refresh expired access tokens
How can we access Keycloak OAuth tokens (idToken, accessToken, refreshToken) in a Better Auth stateless setup?

Specifically:

  • Store them in the JWT session cookie (encrypted)
  • Access them via getSession() API
  • Send accessToken to backend API
  • Use idToken for Keycloak logout
Ideal Solution
We need a way to:
// In auth.ts
export const auth = betterAuth({
  plugins: [
    genericOAuth({ /* ... */ }),
    jwt({
      // ✅ Store OAuth tokens in JWT payload
      includeOAuthTokens: true, // (doesn't exist)
    })
  ]
});

// In any server action/component
const session = await auth.api.getSession({ headers });
const idToken = session.oauth.idToken; // ✅ Works!
Was this page helpful?