@speajus/diblob-oauth
@speajus/diblob-oauth provides OAuth 2.1 / OpenID Connect integration for diblob containers, built on top of openid-client.
It focuses on:
- Authorization Code + PKCE flows for browser-based clients
- Verifying bearer access tokens for APIs
- Optional server-side session management backed by OAuth tokens
Blobs
oauthClientConfig– strongly-typed OAuth client configurationoidcClient– high-level OIDC client for browser login flowsaccessTokenVerifier– verifies bearer tokens for APIsoauthSessionManager– abstraction for server-side sessions
Registration helpers
registerOAuthClientConfigBlob(container, options)registerOidcClientBlobs(container, options)registerAccessTokenVerifier(container, options)registerInMemorySessionManager(container, options?)
Basic usage
Example: configure an AWS Cognito app client and use oidcClient to build the authorization URL, then verify tokens in an API service.
End-to-end login flow (browser → Cognito → API)
The Cognito example in examples/oauth-cognito wires these pieces together:
/login
- Your HTTP handler resolves
oidcClientfrom the container. - Calls
oidcClient.fetchAuthorizationUrl({ state }). - Redirects the browser to the returned URL.
- Your HTTP handler resolves
/callback
- Cognito redirects back with
code(andstate). - The handler resolves
oidcClientandoauthClientConfig. - Calls
oidcClient.exchangeAuthorizationCode({ code, redirectUri, state, expectedState }). - Resolves
oauthSessionManagerand callsoauthSessionManager.createSession({ accessToken, idToken, refreshToken, expiresAt }). - Sets a
sessionIdcookie and redirects to a protected route (for example/me).
- Cognito redirects back with
/me (protected API)
- Reads the
sessionIdcookie. - Resolves
oauthSessionManagerandaccessTokenVerifier. - Calls
oauthSessionManager.fetchSession(sessionId); if missing, returns401. - Calls
accessTokenVerifier.verifyAccessToken(session.accessToken, { requiredScopes }). - On success, returns a small JSON payload (for example
{ subject, scopes }).
- Reads the
In-memory session manager
For simple demos and local development you can use the built-in in-memory session manager:
registerInMemorySessionManager(container);This registers oauthSessionManager as a singleton that stores sessions in memory only. For production scenarios you should provide your own OAuthSessionManager implementation backed by a durable store (for example, Redis or a database).
Logout and post-logout redirect
To integrate IdP logout, configure a post-logout redirect URI on your OAuth client and in OAuthClientConfig:
- Set
postLogoutRedirectUri(for example via anOAUTH_POST_LOGOUT_REDIRECT_URIenvironment variable when usingregisterOAuthClientConfigBlob). - Configure your IdP to allow this URI as a post-logout redirect.
When paths.logout is set on the adapter, both GET /logout and POST /logout will:
- Look up the current session (if any) and capture its
idToken. - Invalidate the
sessionIdviaoauthSessionManager.invalidateSession. - Clear the
sessionIdHttpOnly cookie. - When the IdP exposes an
end_session_endpoint, redirect the browser there usingpost_logout_redirect_uriandid_token_hint.
Generic WHATWG server adapter + async context
For HTTP servers, @speajus/diblob-oauth also provides a generic adapter built on @whatwg-node/server that ties everything together:
- OAuth/OIDC blobs from this package (
oidcClient,oauthSessionManager,accessTokenVerifier,oauthClientConfig) - A request-scoped async context from
@speajus/diblob-async-context
The adapter is exported as:
createOAuthServerAdapter<TContext extends object>(options)OAuthServerAdapterOptions<TContext>
Basic Node HTTP usage
import { randomUUID } from 'node:crypto';
import { createServer } from 'node:http';
import { createBlob, createContainer } from '@speajus/diblob';
import { AsyncLocalStorageContext } from '@speajus/diblob-async-context';
import {
createOAuthServerAdapter,
oauthClientConfig,
registerAccessTokenVerifier,
registerInMemorySessionManager,
registerOAuthClientConfigBlob,
registerOidcClientBlobs,
} from '@speajus/diblob-oauth';
interface RequestContext {
requestId: string;
userId?: string;
}
const requestContext = createBlob<RequestContext>('requestContext');
const container = createContainer();
registerOAuthClientConfigBlob(container);
registerOidcClientBlobs(container);
registerAccessTokenVerifier(container);
registerInMemorySessionManager(container);
const asyncContext = new AsyncLocalStorageContext(container);
asyncContext.registerWithContext(requestContext);
const adapter = createOAuthServerAdapter<RequestContext>({
container,
asyncContext,
contextBlob: requestContext,
initializeContext: () => ({ requestId: randomUUID() }),
applyAuthenticatedResult: (context, result) => {
context.userId = result.subject;
},
});
const server = createServer(adapter);
server.listen(3005);Every request is wrapped in asyncContext.runWithContext(requestContext, context, handler), so downstream code can resolve requestContext from the container and read requestId, userId, etc., while the adapter handles /login, /callback, and /me using the blobs from this package.
Customizing paths, scopes, and hooks
OAuthServerAdapterOptions<TContext> supports several optional settings:
contextBlob– the blob representing your per-request context; used withAsyncLocalStorageContext.runWithContextto scope the current context value per request.paths– override default route paths:login(default/login)callback(default/callback)me(default/me)logout(no route by default; if set,GET/POST /logoutwill invalidate the session, clear the cookie, and when possible redirect the browser to the IdPend_session_endpointusingpost_logout_redirect_uriandid_token_hint.)
requiredScopes– scopes required by/me(default['openid'])onRequest– called for every request after the async context is established:tsonRequest: async ({ request, context }) => { // e.g., log request + requestId using your logger blob }onError– called when an internal error occurs in one of the built-in routes:tsonError: async ({ request, context, error, stage }) => { // stage is 'callback', 'me', or 'logout' }
The examples/oauth-cognito project shows a complete setup that wires this adapter together with a request-scoped context and @speajus/diblob-logger.