@nitrotool/oauth-server
v1.0.5
Published
## Installation
Readme
@nitrotool/oauth-server
Installation
pnpm add @nitrotool/oauth-serverUsage
Authorize Endpoint
// server/api/oauth/authorize.ts
import {
defineOAuthAuthorizeHandler,
isValidRedirectUrl,
oauthError,
} from '@nitrotool/oauth-server';
import { sendRedirect } from 'h3';
export default defineOAuthAuthorizeHandler(async (event, payload) => {
//This will always pass, since we return the id we give
const client = await getOAuthClientById(payload.client_id);
if (!client) {
return oauthError(event, 'unauthorized_client', 'Invalid client ID.');
}
if (!isValidRedirectUrl(payload.redirect_uri, client.redirectUris)) {
return oauthError(event, 'invalid_request', 'Invalid redirect URI.');
}
//TODO: Store & fetch this somewhere safe.
const userId = '1234567890';
const code = 'SOME-RANDOM-CODE';
await saveAuthCode(code, {
client_id: payload.client_id,
redirect_uri: payload.redirect_uri,
user_id: userId,
scope: payload.scope,
code_challenge: payload.code_challenge,
});
const url = new URL(payload.redirect_uri);
url.searchParams.set('code', code);
if (payload.state) {
url.searchParams.set('state', payload.state);
}
return sendRedirect(event, url.toString());
});
OAuth Token Endpoint
// server/api/oauth/token.post.ts
import { defineOAuthTokenHandler, oauthError } from '@nitrotool/oauth-server';
export default defineOAuthTokenHandler(async (event, payload) => {
if (payload.grant_type === 'authorization_code') {
const data = await consumeAuthCode(payload.code);
if (!data)
return oauthError(event, 'invalid_grant', 'Invalid authorization code.');
// Use shared validation
const error = validateTokenRequest(event, payload, data);
if (error) return error;
return issueTokens(data, payload.client_id);
}
if (payload.grant_type === 'refresh_token') {
const data = await getRefreshToken(payload.refresh_token);
if (!data)
return oauthError(event, 'invalid_grant', 'Invalid refresh token.');
// Just issue a new access token for refresh flow
const tokens = await issueTokens(data, payload.client_id);
return {
access_token: tokens.access_token,
token_type: tokens.token_type,
expires_in: tokens.expires_in,
};
}
return oauthError(event, 'unsupported_grant_type');
});OIDC Authorize endpoint
// server/api/oidc/authorize.ts
import {
defineOAuthServerHandler,
oidcAuthorizeSchema, // Use the OIDC schema with nonce/scope requirements
} from '@nitrotool/oauth-server';
import { sendRedirect, getQuery } from 'h3';
export default defineOAuthServerHandler({
schema: oidcAuthorizeSchema,
read: getQuery,
async handler(event, payload) {
// 1. Shared Validation
const { client, error } = await validateAuthorizeRequest(event, payload);
if (error) return error;
// 2. Identity Logic (Typically behind a session check/login)
const userId = '1234567890';
const code = 'OIDC-' + Math.random().toString(36).substring(2);
// 3. Store OIDC Context
// Crucially, we store the 'nonce' and 'scope' provided in the request
await saveAuthCode(code, {
client_id: payload.client_id,
redirect_uri: payload.redirect_uri,
user_id: userId,
scope: payload.scope, // Will include 'openid'
nonce: payload.nonce, // <--- MUST be stored for OIDC
code_challenge: payload.code_challenge,
});
// 4. Redirect back to client
const redirectUrl = buildAuthorizeRedirect(
payload.redirect_uri,
code,
payload.state,
);
return sendRedirect(event, redirectUrl);
},
});
payloadis strongly typed asOIDCAuthQuery.- Always echo back the
stateparameter. - Store the
noncefrompayload.noncewith the authorization code for inclusion in theid_token.
OIDC Token Endpoint
// server/api/oidc/token.ts
import {defineOIDCTokenHandler, OIDCTokenResponse} from '@nitrotool/oauth-server'
export default defineOIDCTokenHandler({
async consumeCode(code) {
return await consumeAuthCode(code);
},
async handler(event, payload, ctx) {
// 1. Validation (Payload must be auth_code grant for OIDC)
const error = validateTokenRequest(event, payload as any, ctx);
if (error) return error;
// 2. Issue Standard Tokens
const tokens = await issueTokens(ctx, payload.client_id);
// 3. Generate OIDC specific ID Token
const idToken = await encodeJwt(
{
sub: ctx.user_id,
aud: payload.client_id,
nonce: ctx.nonce,
type: 'id_token',
},
3600,
);
return {
...tokens,
id_token: idToken,
};
},
});payloadis strongly typed asOIDCTokenPayload.- The return type must conform to
OIDCTokenResponse. access_tokenandexpires_inare required;refresh_token,scope, andid_tokenare optional depending on the grant and requested scopes.
Notes
- No client code: This library is purely for implementing an OAuth/OIDC provider (server).
- You must persist authorization codes,
nonces, and optionally refresh tokens in your database. isValidRedirectUrlhelps safely validate client redirect URIs.
