Version: v0.13
Authorization Server
The IDK provides components for building OAuth 2.0 authorization servers, including token issuance, client management, and standards-compliant metadata endpoints.
Overview
The authorization server module supports:
- Client registration and authentication
- Authorization code and token endpoints
- Token generation with configurable signing
- Metadata endpoint (
.well-known/oauth-authorization-server) - PKCE and DPoP validation
Server Configuration
Configure the authorization server:
- Android/kotlin
- iOS/Swift
val authServer = AuthorizationServerBuilder()
.issuer("https://auth.example.com")
.signingKey(signingKeyPair)
.tokenLifetime(Duration.ofHours(1))
.refreshTokenLifetime(Duration.ofDays(30))
.build()
let authServer = AuthorizationServerBuilder()
.issuer(issuer: "https://auth.example.com")
.signingKey(key: signingKeyPair)
.tokenLifetime(duration: 3600) // 1 hour
.refreshTokenLifetime(duration: 2592000) // 30 days
.build()
Client Registration
Register OAuth 2.0 clients:
- Android/kotlin
- iOS/Swift
// Register a public client (mobile app)
val mobileClient = authServer.registerClient {
clientId = "mobile-app"
clientType = ClientType.PUBLIC
redirectUris = listOf("myapp://callback")
grantTypes = setOf(GrantType.AUTHORIZATION_CODE, GrantType.REFRESH_TOKEN)
scopes = setOf("openid", "profile", "email")
requirePkce = true
}
// Register a confidential client (server-side app)
val webClient = authServer.registerClient {
clientId = "web-app"
clientSecret = generateSecureSecret()
clientType = ClientType.CONFIDENTIAL
redirectUris = listOf("https://app.example.com/callback")
grantTypes = setOf(GrantType.AUTHORIZATION_CODE, GrantType.CLIENT_CREDENTIALS)
scopes = setOf("openid", "profile", "api:read", "api:write")
}
// Register a public client (mobile app)
let mobileClient = try authServer.registerClient { builder in
builder.clientId = "mobile-app"
builder.clientType = .public_
builder.redirectUris = ["myapp://callback"]
builder.grantTypes = Set([.authorizationCode, .refreshToken])
builder.scopes = Set(["openid", "profile", "email"])
builder.requirePkce = true
}
// Register a confidential client (server-side app)
let webClient = try authServer.registerClient { builder in
builder.clientId = "web-app"
builder.clientSecret = generateSecureSecret()
builder.clientType = .confidential
builder.redirectUris = ["https://app.example.com/callback"]
builder.grantTypes = Set([.authorizationCode, .clientCredentials])
builder.scopes = Set(["openid", "profile", "api:read", "api:write"])
}
Authorization Endpoint
Handle authorization requests:
- Android/kotlin
- iOS/Swift
fun handleAuthorizationRequest(request: HttpRequest): HttpResponse {
// Parse and validate the authorization request
val authRequest = authServer.parseAuthorizationRequest(request)
// Validate client
val client = authServer.getClient(authRequest.clientId)
?: return errorResponse("invalid_client", "Unknown client")
// Validate redirect URI
if (!client.redirectUris.contains(authRequest.redirectUri)) {
return errorResponse("invalid_request", "Invalid redirect URI")
}
// Validate PKCE if required
if (client.requirePkce && authRequest.codeChallenge == null) {
return redirectError(authRequest, "invalid_request", "PKCE required")
}
// At this point, authenticate the user and get consent
// Then generate the authorization code
val authCode = authServer.generateAuthorizationCode(
clientId = authRequest.clientId,
subject = authenticatedUser.id,
scopes = approvedScopes,
redirectUri = authRequest.redirectUri,
codeChallenge = authRequest.codeChallenge,
codeChallengeMethod = authRequest.codeChallengeMethod,
nonce = authRequest.nonce
)
// Redirect back to client
val redirectUrl = buildRedirectUrl(
redirectUri = authRequest.redirectUri,
code = authCode,
state = authRequest.state
)
return HttpResponse.redirect(redirectUrl)
}
func handleAuthorizationRequest(request: HttpRequest) async throws -> HttpResponse {
// Parse and validate the authorization request
let authRequest = try authServer.parseAuthorizationRequest(request: request)
// Validate client
guard let client = authServer.getClient(clientId: authRequest.clientId) else {
return errorResponse(error: "invalid_client", description: "Unknown client")
}
// Validate redirect URI
guard client.redirectUris.contains(authRequest.redirectUri) else {
return errorResponse(error: "invalid_request", description: "Invalid redirect URI")
}
// Validate PKCE if required
if client.requirePkce && authRequest.codeChallenge == nil {
return redirectError(request: authRequest, error: "invalid_request", description: "PKCE required")
}
// At this point, authenticate the user and get consent
// Then generate the authorization code
let authCode = try await authServer.generateAuthorizationCode(
clientId: authRequest.clientId,
subject: authenticatedUser.id,
scopes: approvedScopes,
redirectUri: authRequest.redirectUri,
codeChallenge: authRequest.codeChallenge,
codeChallengeMethod: authRequest.codeChallengeMethod,
nonce: authRequest.nonce
)
// Redirect back to client
let redirectUrl = buildRedirectUrl(
redirectUri: authRequest.redirectUri,
code: authCode,
state: authRequest.state
)
return HttpResponse.redirect(url: redirectUrl)
}
Token Endpoint
Handle token requests:
- Android/kotlin
- iOS/Swift
fun handleTokenRequest(request: HttpRequest): HttpResponse {
val tokenRequest = authServer.parseTokenRequest(request)
return when (tokenRequest.grantType) {
GrantType.AUTHORIZATION_CODE -> handleAuthorizationCodeGrant(tokenRequest)
GrantType.REFRESH_TOKEN -> handleRefreshTokenGrant(tokenRequest)
GrantType.CLIENT_CREDENTIALS -> handleClientCredentialsGrant(tokenRequest)
else -> errorResponse("unsupported_grant_type", "Grant type not supported")
}
}
private fun handleAuthorizationCodeGrant(request: TokenRequest): HttpResponse {
// Validate authorization code
val codeData = authServer.validateAuthorizationCode(request.code)
?: return errorResponse("invalid_grant", "Invalid authorization code")
// Validate PKCE
if (codeData.codeChallenge != null) {
if (!authServer.validateCodeVerifier(
codeVerifier = request.codeVerifier,
codeChallenge = codeData.codeChallenge,
method = codeData.codeChallengeMethod
)) {
return errorResponse("invalid_grant", "Invalid code verifier")
}
}
// Generate tokens
val tokens = authServer.issueTokens(
subject = codeData.subject,
clientId = codeData.clientId,
scopes = codeData.scopes,
nonce = codeData.nonce
)
return jsonResponse(tokens)
}
func handleTokenRequest(request: HttpRequest) async throws -> HttpResponse {
let tokenRequest = try authServer.parseTokenRequest(request: request)
switch tokenRequest.grantType {
case .authorizationCode:
return try await handleAuthorizationCodeGrant(request: tokenRequest)
case .refreshToken:
return try await handleRefreshTokenGrant(request: tokenRequest)
case .clientCredentials:
return try await handleClientCredentialsGrant(request: tokenRequest)
default:
return errorResponse(error: "unsupported_grant_type", description: "Grant type not supported")
}
}
Token Generation
Generate access tokens and ID tokens:
- Android/kotlin
- iOS/Swift
val tokens = authServer.issueTokens(
subject = userId,
clientId = clientId,
scopes = scopes,
nonce = nonce,
additionalClaims = mapOf(
"email" to userEmail,
"name" to userName
)
)
// tokens.accessToken - JWT access token
// tokens.idToken - OIDC ID token (if openid scope)
// tokens.refreshToken - Refresh token
// tokens.expiresIn - Seconds until access token expires
// tokens.tokenType - "Bearer" or "DPoP"
let tokens = try await authServer.issueTokens(
subject: userId,
clientId: clientId,
scopes: scopes,
nonce: nonce,
additionalClaims: [
"email": userEmail,
"name": userName
]
)
// tokens.accessToken - JWT access token
// tokens.idToken - OIDC ID token (if openid scope)
// tokens.refreshToken - Refresh token
// tokens.expiresIn - Seconds until access token expires
// tokens.tokenType - "Bearer" or "DPoP"
Metadata Endpoint
Serve authorization server metadata:
fun handleMetadataRequest(): HttpResponse {
val metadata = authServer.getMetadata()
return jsonResponse(metadata)
}
Example metadata response:
{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/authorize",
"token_endpoint": "https://auth.example.com/token",
"jwks_uri": "https://auth.example.com/.well-known/jwks.json",
"scopes_supported": ["openid", "profile", "email"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"]
}
JWKS Endpoint
Serve the JSON Web Key Set:
fun handleJwksRequest(): HttpResponse {
val jwks = authServer.getJwks()
return jsonResponse(jwks)
}