The SDK is server side only, and for one reason: it holds your
client_secret. There is no
React component, hook or provider to import, and nothing to render. Data reaches the browser as
plain props from a server component, which is what the dashboard below does.It has no Node specific imports. Everything cryptographic uses the standard Web Crypto API, so
it runs anywhere with globalThis.crypto: Node, Bun, Deno, the Edge runtime and Workers.Register the application
Open the console, go to Settings, then Applications, and create a Web application.1
Add the redirect URI
It must match what your app sends, exactly. For local development on port 9999 that is
http://localhost:9999/api/auth/callback.2
Select scopes
This example asks for
profile:read, workspaces:read, models:read and usage:read. A
token carries only the intersection of what the user approved and what the application holds,
so a scope you skip here fails later even though the user consented.3
Copy the credentials
You get a
client_id and a client_secret. The secret is shown once and belongs on your
server.Create the app
SESSION_SECRET encrypts the cookie holding the tokens, so generate a real
one and keep it stable.
.env
The OAuth client
Keep one place that builds the client and names the scopes.build_scopes validates as it
builds, so a typo throws here instead of turning into a 403 on an unrelated request later.
lib/oauth.ts
Send the user to consent
authorize_url generates the PKCE pair for you. The verifier and the state have to survive the
round trip, so store them in a short lived httpOnly cookie and read them back in the callback.
app/api/auth/login/route.ts
Handle the callback
Compare the returnedstate against the one you stored before exchanging anything. exchange_code
returns a ready client, and nemu.tokens is what you persist.
app/api/auth/callback/route.ts
Refresh in the right place
A server component cannot write cookies in Next.js. So the refresh must not happen during a page render. Put it inproxy.ts, which runs before the page and can write to the response.
proxy.ts
lib/client.ts
Call the API
The page is a server component. It calls the API and hands plain data to the interface, so no token is ever serialized to the browser.app/dashboard/page.tsx
toJSON() strips the entity’s methods and internal client, leaving a plain object that crosses
the server and client boundary cleanly.
Storing the session
The example seals tokens into an httpOnly cookie with AES-256-GCM, using Web Crypto and a key derived with PBKDF2 fromSESSION_SECRET. It keeps the whole thing in one file with no
database, which is what makes it easy to read. A realistic token pair seals to roughly 800
bytes.
Whatever you store it in, the rules do not change. Tokens and the client_secret stay on the
server, cookies are httpOnly and secure in production, and sameSite is lax so the cookie
survives the redirect back from consent.
Run it
http://localhost:9999, sign in, and the dashboard shows your account and workspaces read
straight from the API.
The full example
The complete app is in the repository underothers/examples/oauth-sdk-nextjs, including the
sealed cookie implementation, the error states on the sign in screen, and the design tokens.
Prefer Auth.js
If your app already uses Auth.js, or you want the session handling solved for you, there is a second example that wires Nemu in as a custom Auth.js provider instead of doing the flow by hand.All examples
Both example apps, and which one to start from.
Scopes
Every scope, the aliases, and the rest of what
build_scopes accepts.