This recipe uses the Dynamic JavaScript SDK (
@dynamic-labs-sdk/client + @dynamic-labs-sdk/react-hooks). The SDK is headless — render your own auth UI and call the SDK’s auth functions on submit. See the React Quickstart for the full setup.Pre-requisites
- Cloned the nextAuth example repo and installed dependencies.
Steps
Add the right env variables
You’ll need to define two environment variables in your.env.local file:
.env.local file, and you’re good to go.
Install the Dynamic JavaScript SDK
Create the Dynamic client module
The client is a module-level singleton — create it once and import it from anywhere. Import this file at app root so extensions register before any component renders.app/lib/dynamicClient.ts
Add the DynamicProvider
In Next.js App Router, providers need to live in a client component so they can hold reactive state. Create a wrapper and import the client module so extensions register on mount:app/components/dynamic-provider-wrapper.tsx
app/layout.tsx:
app/layout.tsx
Build a login component
The JS SDK is headless — there’s no built-in widget. Build a minimal login form usingsendEmailOTP and verifyOTP:
app/components/login.tsx
app/components/header.tsx
tokenChanged event Dynamic provides, but let’s come back to that and first add the server side code.
Define the JWT decoding
NextAuth needs to know how to decode and validate the JWT which Dynamic sends back. To do this we’ll create a custom JWT decoder inside a new helper file:app/lib/authHelpers.ts
getKey function which is used to fetch the public key which you can use to decode the JWT. This function will make an API call to Dynamic. We’ll add this to the same file:
app/lib/authHelpers.ts
Update the NextAuth configuration
You can copy the below code and paste it over the full existing auth.ts file in the demo repo:./auth.ts
Trigger the JWT validation on login
In the old React Core SDK you could pass anonAuthSuccess callback into DynamicContextProvider settings. The JS SDK is event-driven instead — listen for tokenChanged and forward the new JWT to NextAuth. Add a NextAuthBridge component inside DynamicProvider:
app/components/nextauth-bridge.tsx
app/components/dynamic-provider-wrapper.tsx
useEvent cleans up the subscription on unmount, so it’s safe under React Strict Mode. We’re using the getCsrfToken function which NextAuth provides — this is important because NextAuth uses CSRF tokens to prevent CSRF attacks.
Token Scopes
A JWT token includes ascope claim (a space-separated list of scopes) that indicates the user’s authentication state. You must verify that the scope list includes user:basic to confirm the user has fully completed authentication.
Our SDK handles this for the frontend, but for the backend you will need to check this scope and handle it accordingly.
To do this, add a scope verification check in the authorize function in the auth.ts file:
./auth.ts
user:basic. These tokens represent incomplete authentication and should never be trusted for protected operations.
Run the example
Going further
You’ll see inauth.ts that we are assigning certain JWT fields to a user object. You can add any fields you want to this object, and then access them in your pages via the useSession hook.