Authorization ‐ OAuth2 ‐ Tuto 4 ‐ Token Issuance with tokenRoute - demingongo/kaapi GitHub Wiki
Token Issuance with tokenRoute
The tokenRoute() method is used to define the behavior of the token endpoint (/oauth2/token) in any OAuth2/OIDC flow using the @kaapi/oauth2-auth-design package.
It is available in all flow builders (Authorization Code, Client Credentials, Device Authorization) and allows you to configure the token issuing logic and endpoint path.
🔧 Route Configuration
Within tokenRoute(), you can configure the route with the following methods:
| Method | Description |
|---|---|
setPath(path) |
Changes the default token endpoint path. Default: /oauth2/token. |
generateToken(fn) |
Implements the core logic for validating requests and issuing tokens. |
🧩 generateToken() Overview
The generateToken() function is the core of the token exchange process. It receives:
- Input context: An object containing validated and parsed parameters (such as grant type, client ID, secrets, codes, helper functions, etc.).
- Request object: The original HTTP request, if additional inspection is needed.
You must return one of the following:
- An
OAuth2TokenResponseto issue a token. - An error object with an
OAuth2ErrorCode. nullto return a genericinvalid_requesterror.
✅ Example: Authorization Code Flow
import {
ClientSecretBasic,
NoneAuthMethod,
OAuth2ErrorCode,
OAuth2TokenResponse,
OIDCAuthorizationCodeBuilder
} from '@kaapi/oauth2-auth-design'
export default OIDCAuthorizationCodeBuilder
.create()
.setTokenTTL(3600) // 1 hour
.addClientAuthenticationMethod(new ClientSecretBasic())
.addClientAuthenticationMethod(new NoneAuthMethod())
.useAccessTokenJwks(true)
.setScopes({
openid: 'User identity',
email: 'Access to user email',
})
.tokenRoute(route =>
route
.setPath('/oauth2/token')
.generateToken(async ({
grantType,
clientId,
ttl,
tokenType,
code,
codeVerifier,
clientSecret,
createJwtAccessToken,
createIdToken,
verifyCodeVerifier
}, request) => {
// Example error
// return { error: OAuth2ErrorCode.INVALID_CLIENT, error_description: 'Client not found' }
// Example success
return new OAuth2TokenResponse({ access_token: 'generated-access-token' })
.setExpiresIn(ttl)
.setTokenType(tokenType)
.setScope('openid email')
.setRefreshToken('optional-refresh-token')
.setIdToken('optional-id-token')
})
)
Handler Notes – Authorization Code Flow
codeandcodeVerifierare used for PKCE validation.verifyCodeVerifier()should be used to validate thecode_challenge.- Use
createJwtAccessToken()andcreateIdToken()to generate signed tokens when using JWKS.
✅ Example: Client Credentials Flow
import {
ClientSecretBasic,
OAuth2ClientCredentialsBuilder,
OAuth2ErrorCode,
OAuth2TokenResponse
} from '@kaapi/oauth2-auth-design'
export default OAuth2ClientCredentialsBuilder
.create()
.setTokenTTL(600) // 10 minutes
.addClientAuthenticationMethod(new ClientSecretBasic())
.useAccessTokenJwks(true)
.tokenRoute(route =>
route
.setPath('/oauth2/token')
.generateToken(async ({
grantType,
clientId,
ttl,
tokenType,
clientSecret,
scope,
createJwtAccessToken,
createIdToken
}, request) => {
// Example success
return new OAuth2TokenResponse({ access_token: 'generated-access-token' })
.setExpiresIn(ttl)
.setTokenType(tokenType)
.setScope(scope ?? '')
})
)
Handler Notes – Client Credentials Flow
- No user interaction or authorization code is involved.
scopeis optional and typically predefined for the client.- Ideal for server-to-server authentication.
✅ Example: Device Authorization Flow
import {
NoneAuthMethod,
OAuth2DeviceAuthorizationBuilder,
OAuth2ErrorCode,
OAuth2TokenResponse
} from '@kaapi/oauth2-auth-design'
export default OAuth2DeviceAuthorizationBuilder
.create()
.setTokenTTL(600) // 10 minutes
.addClientAuthenticationMethod(new NoneAuthMethod())
.tokenRoute(route =>
route
.setPath('/oauth2/token')
.generateToken(async ({
grantType,
clientId,
ttl,
tokenType,
deviceCode,
clientSecret,
createJwtAccessToken,
createIdToken
}, request) => {
// Example polling check
const deviceValidated = checkDeviceCode(deviceCode); // your logic
if (!deviceValidated) {
return { error: OAuth2ErrorCode.AUTHORIZATION_PENDING }
}
return new OAuth2TokenResponse({ access_token: 'generated-access-token' })
.setExpiresIn(ttl)
.setTokenType(tokenType)
})
)
Handler Notes – Device Authorization Flow
deviceCodeidentifies the device/user code pair.- Can return specific polling errors like
authorization_pendingorslow_down. - Commonly used in devices without a browser or limited input.
🧾 Return Values
You can return one of the following from generateToken():
✅ Successful Token Response
return new OAuth2TokenResponse({ access_token: '...' })
.setExpiresIn(ttl)
.setTokenType(tokenType)
.setScope('scope1 scope2')
.setRefreshToken('...')
.setIdToken('...')
❌ Error Response
return {
error: OAuth2ErrorCode.INVALID_CLIENT,
error_description: 'Client credentials are invalid'
}
❌ Generic Error (invalid_request)
return null
🔐 Security Note
Always validate the client, credentials, and any user-facing inputs (e.g., code, deviceCode, scope). Avoid issuing tokens without proper verification. Use verifyCodeVerifier, createJwtAccessToken, and related helpers to enforce security best practices.
📌 Summary
tokenRoute()defines how the token endpoint works.- Used in all OAuth2/OIDC flows.
generateToken()gives full control over token issuance logic.- Ensure validation and security checks for all flows.