Get started
Works with a new project or one you already have. Every path below is relative to the project root, and the App Router layout is what the sample uses. Requires Node.js 18 or later and a ThunderID application with an OAuth 2.0 or OIDC (`authorization_code`) client.
Start from a Next.js app
Create one, or skip this step and use the app you already have. An existing App Router project needs no restructuring.
npx create-next-app@latest my-app --typescript --app cd my-app
Install the packages
Better Auth does the OAuth 2.0 and OIDC work; the helper supplies the ThunderID configuration.
npm install better-auth @thunderid/better-auth
Add the environment variables
`THUNDERID_CLIENT_ID` and `THUNDERID_CLIENT_SECRET` come from the application's Credentials tab in the ThunderID console. `BETTER_AUTH_URL` is the base URL this app is served from, and the callback URL is derived from it.
BETTER_AUTH_SECRET=<run: openssl rand -base64 32> BETTER_AUTH_URL=http://localhost:3000 THUNDERID_ISSUER=https://localhost:8090 THUNDERID_CLIENT_ID=<your-client-id> THUNDERID_CLIENT_SECRET=<your-client-secret>
Against a ThunderID instance with a self-signed certificate, local development also needs `NODE_TLS_REJECT_UNAUTHORIZED=0`. It disables TLS verification for the whole process, so never set it outside local development.
Create the Better Auth server instance
This is the file that wires ThunderID in: `thunderid()` returns the provider configuration, and the Generic OAuth plugin registers it. `nextCookies()` must come last in the plugin array, since it only applies cookies set by plugins before it.
import {thunderid} from '@thunderid/better-auth';
import {betterAuth} from 'better-auth';
import {nextCookies} from 'better-auth/next-js';
import {genericOAuth} from 'better-auth/plugins';
export const auth = betterAuth({
// Point this at your own database. See better-auth.com/docs/adapters.
database: yourDatabaseAdapter,
plugins: [
genericOAuth({
config: [
thunderid({
clientId: process.env.THUNDERID_CLIENT_ID!,
clientSecret: process.env.THUNDERID_CLIENT_SECRET!,
issuer: process.env.THUNDERID_ISSUER!,
}),
],
}),
nextCookies(),
],
});Mount the route handler
A single catch-all route exposes every Better Auth endpoint, including the callback the next step registers.
import {toNextJsHandler} from 'better-auth/next-js';
import {auth} from '../../../../lib/auth';
export const {GET, POST} = toNextJsHandler(auth);Register the redirect URI
Add this callback URL to the same ThunderID application. The path is fixed by the route handler above and the provider id, so only the origin changes between environments.
http://localhost:3000/api/auth/callback/thunderid
Create the client
No client plugin is needed. The Generic OAuth plugin exposes sign-in through the standard social-provider API.
import {createAuthClient} from 'better-auth/react';
export const authClient = createAuthClient();Sign in and out from a component
Any client component can read the session and start the flow.
'use client';
import {authClient} from '../lib/auth-client';
export default function Home() {
const {data: session, isPending} = authClient.useSession();
if (isPending) return null;
if (!session) {
return (
<button onClick={() => authClient.signIn.social({provider: 'thunderid', callbackURL: '/'})}>
Sign in with ThunderID
</button>
);
}
return (
<>
<p>Signed in as {session.user.email}</p>
<button onClick={() => authClient.signOut()}>Sign out</button>
</>
);
}Options
Full referenceissuer is passed through from Better Auth's BaseOAuthProviderOptions and behaves exactly as it does for the built-in provider helpers.thunderid()Returns a typed GenericOAuthConfig for a ThunderID issuer.
issuerrequiredstringThunderID issuer URL, for example https://thunderid.example.com. A trailing slash is trimmed. The OIDC discovery URL is derived as {issuer}/.well-known/openid-configuration, so all endpoints come from the discovery document.
clientIdrequiredstringOAuth client ID.
clientSecretoptionalstringOAuth client secret. Omit for public clients using tokenEndpointAuth: {method: 'none'}.
scopesoptionalstring[]Requested scopes. Defaults to ['openid', 'profile', 'email'].
tokenEndpointAuthoptionalobjectToken endpoint authentication method, for example {method: 'client_secret_post'}.
pkceoptionalbooleanForce PKCE on or off. Better Auth enables PKCE by default.
redirectURIoptionalstringOverride the callback URL. It must still resolve to Better Auth's callback route for this provider, which ends in `/callback/thunderid`.
endSessionEndpointoptionalstringRP-initiated logout endpoint. Defaults to the discovery document.
postLogoutRedirectURIoptionalstringWhere ThunderID returns the user after logout.
disableProviderLogoutoptionalbooleanSkip provider logout on sign-out. Defaults to false.
disableImplicitSignUpoptionalbooleanRequire an explicit sign-up request before creating a user. Defaults to false.
disableSignUpoptionalbooleanReject sign-in for users who do not already exist. Defaults to false.
overrideUserInfooptionalbooleanRefresh the stored user profile from ThunderID on every sign-in. Defaults to false.