OIDC provider
Act as an OpenID Connect identity provider.
KavachOS can act as a full OpenID Connect identity provider. External apps register as clients and authenticate their users through KavachOS using the standard authorization code flow with PKCE, ID tokens, refresh tokens, discovery, and JWKS.
This is distinct from using OAuth providers with KavachOS. Here, KavachOS is the provider.
Setup
Generate a signing key
import { generateKeyPair, exportJWK } from 'jose';
const { privateKey } = await generateKeyPair('RS256');Store the private key securely. The corresponding public key is exposed at the JWKS endpoint so clients can verify tokens.
Create the module
import { generateKeyPair } from 'jose';
import { createOidcProviderModule } from 'kavachos/auth';
const { privateKey } = await generateKeyPair('RS256');
const oidc = createOidcProviderModule(
{
issuer: 'https://auth.example.com',
signingKey: privateKey,
},
db,
async (userId, scopes) => {
const user = await getUser(userId);
return {
sub: user.id,
email: scopes.includes('email') ? user.email : undefined,
name: scopes.includes('profile') ? user.name : undefined,
emailVerified: user.emailVerified,
};
},
);The third argument is a GetUserClaimsFn callback. KavachOS calls it to build ID tokens and userinfo responses. You control what data is returned per scope.
Register a client
const result = await oidc.registerClient({
clientName: 'My App',
redirectUris: ['https://app.example.com/callback'],
});
if (result.success) {
console.log(result.data.clientId);
console.log(result.data.clientSecret); // shown once, store it now
}The clientSecret is only returned on registration. It is stored hashed. If you lose it, delete and re-register the client.
Configuration options
| Option | Type | Default | Description |
|---|---|---|---|
issuer | string | required | Issuer URL, e.g. https://auth.example.com |
signingKey | CryptoKey | JWK | required | RSA or EC private key for signing tokens |
signingAlgorithm | string | RS256 | JWT algorithm (RS256, ES256, etc.) |
accessTokenTtl | number | 3600 | Access token lifetime in seconds |
refreshTokenTtl | number | 2592000 | Refresh token lifetime in seconds (30 days) |
authCodeTtl | number | 600 | Authorization code lifetime in seconds |
idTokenTtl | number | 3600 | ID token lifetime in seconds |
supportedScopes | string[] | ['openid', 'profile', 'email'] | Scopes this provider accepts |
Authorization code flow
- Client redirects user to
{issuer}/authorizewithresponse_type=code,client_id,redirect_uri,scope, and optionallycode_challenge+code_challenge_method=S256. - Your app authenticates the user, then calls
oidc.authorize({ ...params, userId })to issue a code. - Client exchanges the code at
{issuer}/tokenforaccess_token,id_token, andrefresh_token. - Client can refresh tokens using
grant_type=refresh_token. Refresh tokens rotate on each use.
PKCE with S256 is supported. If the authorization request includes a code_challenge, the token request must include the matching code_verifier. Authorization codes are single-use and expire after 10 minutes by default.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /.well-known/openid-configuration | Discovery document |
| GET | /.well-known/jwks.json | Public signing keys |
| GET | /authorize | Authorization endpoint |
| POST | /token | Token endpoint |
| GET/POST | /userinfo | UserInfo endpoint |
| POST | /register | Dynamic client registration |