Skip to main content

Authorization Code Flow

Standard OAuth 2.0 authorization code grant with optional PKCE support, plus scope discovery and token exchange.

OAuth Flow Overview

  1. Client redirects user to POST /oauth/authorize with client_id, redirect_uri, scope, and response_type=code
  2. User authorizes the application (sets authorize: true)
  3. Server issues authorization code and redirects to redirect_uri with the code
  4. Client exchanges code via POST /oauth/access_token with the authorization code and client credentials
  5. Server issues tokens - access token (and optionally refresh token)

GET /api/oauth/scopes

List all available OAuth scopes.

Authentication: Required

Response:

{
"_embedded": {
"items": [
{
"id": "read:dataset",
"name": "Read Datasets",
"description": "Read-only access to datasets",
"isDefault": true
},
{
"id": "write:dataset",
"name": "Write Datasets",
"description": "Read/write access to datasets",
"isDefault": true
},
{
"id": "read:global",
"name": "Read Global",
"description": "Read-only access to any endpoint"
},
{
"id": "write:global",
"name": "Write Global",
"description": "Full access to any endpoint"
}
]
}
}
FieldDescription
isDefaultIf true, this scope is requested by default when no scopes are specified

POST /api/oauth/authorize

Process an authorization request. If the user authorizes, generates an authorization code and redirects to the client's redirect URI.

Authentication: Required

Payload:

{
"authorize": true,
"response_type": "code",
"client_id": "a6bd8f0f72b2c3275ff6",
"redirect_uri": "https://myapp.example.com/callback",
"scope": "read:dataset write:dataset read:profile",
"state": "random-csrf-token",
"code_challenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
"code_challenge_method": "S256"
}
FieldTypeRequiredDescription
authorizebooleanNoWhether the user authorizes the request (default: false)
response_typestringNoMust be "code" (default: "code")
client_idstringYesOAuth client ID
redirect_uristringYesMust match a registered redirect URI
scopestringYesSpace-separated list of scopes
statestringNoOpaque value for CSRF protection (returned unchanged)
code_challengestringPKCERequired for PKCE-enabled clients
code_challenge_methodstringNo"plain" or "S256" (default: "plain")
proxystringNoJWT for proxy-based redirect flows

Success Response: 302 Redirect

https://myapp.example.com/callback?code=AUTH_CODE&state=random-csrf-token

The authorization code expires after 60 seconds and is single-use.

Error Redirects:

If the user denies or validation fails, the redirect includes error parameters:

ErrorDescription
access_deniedUser did not authorize
invalid_requestPKCE parameters missing for PKCE client
invalid_scopeOne or more requested scopes are invalid
unsupported_response_typeresponse_type is not "code"
server_errorUnexpected server error
https://myapp.example.com/callback?error=access_denied&error_description=User+denied+access&state=random-csrf-token

POST /api/oauth/access_token

Exchange an authorization code or refresh token for access tokens.

Authentication: Via client credentials (Basic auth or payload fields)

Authorization Code Grant

Payload:

{
"grant_type": "authorization_code",
"code": "AUTH_CODE",
"redirect_uri": "https://myapp.example.com/callback",
"client_id": "a6bd8f0f72b2c3275ff6",
"client_secret": "cs_a1b2c3d4...",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}
FieldTypeRequiredDescription
grant_typestringYesMust be "authorization_code"
codestringYesAuthorization code from the authorize step
redirect_uristringNoMust match the URI used in the authorize request
client_idstringYesOAuth client ID (or via Basic auth)
client_secretstringConditionalRequired unless PKCE is used (or via Basic auth)
code_verifierstringPKCERequired if code_challenge was provided during authorization

Refresh Token Grant

Payload:

{
"grant_type": "refresh_token",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"client_id": "a6bd8f0f72b2c3275ff6",
"client_secret": "cs_a1b2c3d4..."
}
FieldTypeRequiredDescription
grant_typestringYesMust be "refresh_token"
refresh_tokenstringYesPreviously issued refresh token
client_idstringYesOAuth client ID
client_secretstringConditionalRequired unless PKCE client

Response:

{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"scope": "read:dataset write:dataset read:profile",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 900
}
FieldDescription
access_tokenJWT for authenticating API requests
token_typeAlways "bearer"
scopeGranted scopes (space-separated)
refresh_tokenOnly present if the client has enableRefreshTokens: true
expires_inToken lifetime in seconds (900 = 15 minutes, only when refresh tokens are enabled)
Client Authentication

Client credentials can be provided either as payload fields (client_id, client_secret) or via HTTP Basic authentication (Authorization: Basic base64(client_id:client_secret)).

Refresh Token Rotation

Refresh tokens are rotated on each use. The previous refresh token is invalidated. If a previously-used refresh token is presented, it indicates a potential replay attack and the server will reject it.

Error Responses:

  • 400 Bad Request - Invalid grant, expired code, or invalid PKCE verifier
  • 401 Unauthorized - Invalid client credentials