Calimatic OIDC/OAuth2 Provider — API Reference
Base URL:
https://auth.calimatic.comIssuer:
https://auth.calimatic.comSpec compliance: OAuth 2.0 (RFC 6749), OpenID Connect Core 1.0, PKCE (RFC 7636), Token Introspection (RFC 7662), Token Revocation (RFC 7009), Dynamic Client Registration (RFC 7591), RP-Initiated Logout 1.0
Table of Contents
- Overview
- Discovery
- Client Registration
- Authorization Endpoint
- Token Endpoint
- UserInfo Endpoint
- JWKS Endpoint
- Token Introspection
- Token Revocation
- End Session (Logout)
- Scopes & Claims
- Error Responses
- Rate Limits
- Token Lifetimes
- Security Considerations
1. Overview
The Calimatic Auth platform acts as a standards-compliant OpenID Connect / OAuth 2.0 Provider. External applications (both first-party Calimatic apps and third-party integrations) authenticate users by redirecting them to the authorization endpoint, which presents a branded login page. After authentication and consent, the platform issues its own RSA-signed JWTs.
Supported Flows
| Flow | Grant Type | Use Case |
|---|---|---|
| Authorization Code + PKCE | authorization_code | Web apps, SPAs, mobile apps |
| Refresh Token | refresh_token | Renewing expired access tokens |
| Client Credentials | client_credentials | Machine-to-machine (no user) |
Token Format
| Token | Format | Signing |
|---|---|---|
| Access Token | JWT | RS256 |
| ID Token | JWT | RS256 |
| Refresh Token | Opaque (base64url) | N/A (stored hashed in DB) |
2. Discovery
GET /.well-known/openid-configuration
Returns the OpenID Provider Configuration Document. All endpoint URLs, supported features, and signing algorithms are advertised here.
Response headers:
Cache-Control: public, max-age=3600
Access-Control-Allow-Origin: *
Example response:
{
"issuer": "https://auth.calimatic.com",
"authorization_endpoint": "https://auth.calimatic.com/api/v1/oidc/authorize",
"token_endpoint": "https://auth.calimatic.com/api/v1/oidc/token",
"userinfo_endpoint": "https://auth.calimatic.com/api/v1/oidc/userinfo",
"jwks_uri": "https://auth.calimatic.com/api/v1/oidc/jwks",
"registration_endpoint": "https://auth.calimatic.com/api/v1/oidc/register",
"revocation_endpoint": "https://auth.calimatic.com/api/v1/oidc/revoke",
"introspection_endpoint": "https://auth.calimatic.com/api/v1/oidc/introspect",
"end_session_endpoint": "https://auth.calimatic.com/api/v1/oidc/end-session",
"scopes_supported": [
"openid", "profile", "email", "phone",
"organization", "permissions", "offline_access"
],
"response_types_supported": ["code"],
"response_modes_supported": ["query"],
"grant_types_supported": [
"authorization_code", "refresh_token", "client_credentials"
],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"token_endpoint_auth_methods_supported": [
"client_secret_post", "client_secret_basic"
],
"code_challenge_methods_supported": ["S256"],
"claims_supported": [
"sub", "iss", "aud", "exp", "iat", "nonce",
"name", "given_name", "family_name",
"email", "email_verified", "picture",
"phone_number", "updated_at",
"organization_id", "organization_memberships", "user_type",
"permissions", "roles"
]
}
3. Client Registration
3.1 Dynamic Registration (RFC 7591)
POST /api/v1/oidc/register
Content-Type: application/json
Rate limit: 5 requests/minute per IP.
Request body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
client_name | string | Yes | — | Human-readable name shown on consent screen |
redirect_uris | string[] | Yes | — | Allowed callback URLs (HTTPS required, except localhost) |
application | string | No | Auto-generated | Unique application identifier |
grant_types | string[] | No | ["authorization_code"] | authorization_code, refresh_token, client_credentials |
token_endpoint_auth_method | string | No | "client_secret_post" | client_secret_post, client_secret_basic, none |
scope | string | No | "openid profile email" | Space-separated default scopes |
client_uri | string | No | — | Application homepage URL |
logo_uri | string | No | — | Logo URL for consent screen |
Example request:
{
"client_name": "Partners Portal",
"redirect_uris": [
"https://partners.calimatic.com/auth/callback",
"http://localhost:3001/auth/callback"
],
"grant_types": ["authorization_code", "refresh_token"],
"scope": "openid profile email organization",
"client_uri": "https://partners.calimatic.com",
"logo_uri": "https://partners.calimatic.com/logo.png"
}
Response (201 Created):
{
"client_id": "cca_aBcDeFgHiJkL...",
"client_secret": "ccas_xYzAbCdEfGhI...",
"client_secret_expires_at": 0,
"client_name": "Partners Portal",
"redirect_uris": [
"https://partners.calimatic.com/auth/callback",
"http://localhost:3001/auth/callback"
],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "client_secret_post",
"scope": "openid profile email organization",
"client_uri": "https://partners.calimatic.com",
"logo_uri": "https://partners.calimatic.com/logo.png"
}
Important: The
client_secretis returned exactly once. Store it securely. It cannot be retrieved again. If lost, rotate the secret via the admin API.
3.2 Public Clients (SPAs / Mobile)
Set token_endpoint_auth_method to "none" during registration. Public clients:
- Do not receive a
client_secret - Must use PKCE (
code_challenge+code_verifier) - Cannot use
client_credentialsgrant
4. Authorization Endpoint
GET /api/v1/oidc/authorize
Initiates the OAuth 2.0 Authorization Code flow. The user is presented with a branded login page, and after authentication + consent, redirected back to the client with an authorization code.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | string | Yes | The registered client ID |
redirect_uri | string | Yes | Must exactly match a registered redirect URI |
response_type | string | Yes | Must be code |
scope | string | No | Space-separated scopes (default: openid) |
state | string | Recommended | Opaque value for CSRF protection |
code_challenge | string | Conditional | S256 PKCE challenge (required if client has require_pkce: true) |
code_challenge_method | string | Conditional | Must be S256 |
nonce | string | No | Random value bound to the ID token |
Flow
1. Client redirects user to /api/v1/oidc/authorize?...
2. Platform validates client_id, redirect_uri, scopes, PKCE
3. If user not logged in → redirect to /login (branded, preserving OAuth params)
4. User authenticates (email/password, SSO, or social login)
5. If consent required and not yet granted → redirect to /consent
6. User approves consent
7. Platform issues authorization code (60-second expiry, one-time use)
8. Redirect to redirect_uri?code=...&state=...
Example
GET /api/v1/oidc/authorize
?client_id=cca_aBcDeFgHiJkL...
&redirect_uri=https://partners.calimatic.com/auth/callback
&response_type=code
&scope=openid+profile+email+organization
&state=af0ifjsldkj
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&nonce=n-0S6_WzA2Mj
Success Redirect
HTTP/1.1 302 Found
Location: https://partners.calimatic.com/auth/callback
?code=dGhpcyBpcyBhIHRlc3QgY29kZQ...
&state=af0ifjsldkj
Error Redirect
HTTP/1.1 302 Found
Location: https://partners.calimatic.com/auth/callback
?error=access_denied
&error_description=The+user+denied+the+authorization+request
&state=af0ifjsldkj
Errors before redirect_uri is validated (invalid client_id or unregistered redirect_uri) return a JSON response directly to prevent open redirector attacks.
5. Token Endpoint
POST /api/v1/oidc/token
Content-Type: application/x-www-form-urlencoded
Rate limit: 20 requests/minute per client.
Client Authentication
Confidential clients must authenticate at the token endpoint using one of:
client_secret_post (body parameters):
client_id=cca_aBcDeFgHiJkL...&client_secret=ccas_xYzAbCdEfGhI...
client_secret_basic (Authorization header):
Authorization: Basic base64(client_id:client_secret)
Public clients pass only client_id in the body (no secret).
5.1 Authorization Code Grant
Exchange an authorization code for tokens.
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | Yes | authorization_code |
code | string | Yes | The authorization code received from the authorize redirect |
redirect_uri | string | Yes | Must match the URI used in the authorization request |
client_id | string | Yes | Client identifier |
client_secret | string | Conditional | Required for confidential clients |
code_verifier | string | Conditional | PKCE verifier (required if code_challenge was sent) |
Example request:
curl -X POST https://auth.calimatic.com/api/v1/oidc/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=dGhpcyBpcyBhIHRlc3QgY29kZQ..." \
-d "redirect_uri=https://partners.calimatic.com/auth/callback" \
-d "client_id=cca_aBcDeFgHiJkL..." \
-d "client_secret=ccas_xYzAbCdEfGhI..." \
-d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
Response (200 OK):
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "xYzAbCdEfGhIjKlMnOpQrStUv...",
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"scope": "openid profile email organization"
}
5.2 Refresh Token Grant
Exchange a refresh token for a new access token.
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | Yes | refresh_token |
refresh_token | string | Yes | The refresh token |
client_id | string | Yes | Client identifier |
client_secret | string | Conditional | Required for confidential clients |
scope | string | No | Subset of originally granted scopes |
Example request:
curl -X POST https://auth.calimatic.com/api/v1/oidc/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=xYzAbCdEfGhIjKlMnOpQrStUv..." \
-d "client_id=cca_aBcDeFgHiJkL..." \
-d "client_secret=ccas_xYzAbCdEfGhI..."
Response (200 OK):
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "xYzAbCdEfGhIjKlMnOpQrStUv...",
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"scope": "openid profile email organization"
}
Note: The same refresh token is returned. Refresh tokens are not rotated on each use.
5.3 Client Credentials Grant
Obtain a machine-to-machine access token without user context.
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | Yes | client_credentials |
client_id | string | Yes | Client identifier |
client_secret | string | Yes | Client secret |
scope | string | No | Space-separated scopes |
Example request:
curl -X POST https://auth.calimatic.com/api/v1/oidc/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=cca_aBcDeFgHiJkL..." \
-d "client_secret=ccas_xYzAbCdEfGhI..." \
-d "scope=openid"
Response (200 OK):
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid"
}
Note: No
refresh_tokenorid_tokenis issued for client credentials. Thesubclaim is theclient_id.
6. UserInfo Endpoint
GET /api/v1/oidc/userinfo
Authorization: Bearer <access_token>
Also accepts POST with the same Authorization header.
Returns claims about the authenticated user based on the scopes granted to the access token.
Example request:
curl https://auth.calimatic.com/api/v1/oidc/userinfo \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."
Example response (scopes: openid profile email organization):
{
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Jane Smith",
"given_name": "Jane",
"family_name": "Smith",
"picture": "https://lh3.googleusercontent.com/photo.jpg",
"email": "jane@example.com",
"email_verified": true,
"organization_id": "org-uuid-here",
"organization_memberships": [
{
"organization_id": "org-uuid-here",
"role": "admin"
}
],
"user_type": "customer_admin"
}
Response headers:
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
7. JWKS Endpoint
GET /api/v1/oidc/jwks
Returns the JSON Web Key Set containing the platform's public RSA keys. Use these to verify JWTs (access tokens and ID tokens) issued by the platform.
Response headers:
Cache-Control: public, max-age=900
Access-Control-Allow-Origin: *
Example response:
{
"keys": [
{
"kty": "RSA",
"n": "0vx7agoebGcQ...",
"e": "AQAB",
"kid": "a1b2c3d4e5f6...",
"alg": "RS256",
"use": "sig"
}
]
}
The JWKS may contain up to two keys (current + previous) during key rotation. Clients should match the kid from the JWT header to find the correct verification key.
8. Token Introspection
POST /api/v1/oidc/introspect
Content-Type: application/x-www-form-urlencoded
RFC 7662 compliant. Returns metadata about a token. Requires client authentication.
Rate limit: 60 requests/minute per client.
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | The token to introspect |
token_type_hint | string | No | access_token or refresh_token |
client_id | string | Yes | Client identifier |
client_secret | string | Yes | Client secret |
Example request:
curl -X POST https://auth.calimatic.com/api/v1/oidc/introspect \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=eyJhbGciOiJSUzI1NiIs..." \
-d "token_type_hint=access_token" \
-d "client_id=cca_aBcDeFgHiJkL..." \
-d "client_secret=ccas_xYzAbCdEfGhI..."
Response (active access token):
{
"active": true,
"scope": "openid profile email",
"client_id": "cca_aBcDeFgHiJkL...",
"token_type": "Bearer",
"exp": 1700000000,
"iat": 1699996400,
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"iss": "https://auth.calimatic.com",
"aud": "cca_aBcDeFgHiJkL..."
}
Response (inactive/invalid token):
{
"active": false
}
9. Token Revocation
POST /api/v1/oidc/revoke
Content-Type: application/x-www-form-urlencoded
RFC 7009 compliant. Revokes a refresh token. Requires client authentication.
Rate limit: 20 requests/minute per client.
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | The token to revoke |
token_type_hint | string | No | refresh_token (default) or access_token |
client_id | string | Yes | Client identifier |
client_secret | string | Yes | Client secret |
Example request:
curl -X POST https://auth.calimatic.com/api/v1/oidc/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=xYzAbCdEfGhIjKlMnOpQrStUv..." \
-d "token_type_hint=refresh_token" \
-d "client_id=cca_aBcDeFgHiJkL..." \
-d "client_secret=ccas_xYzAbCdEfGhI..."
Response: 200 OK (empty body)
Note: Access tokens are stateless JWTs and cannot be revoked. They expire naturally based on
expires_in. Passing an access token returns 200 but has no effect. Always returns 200 per RFC 7009.
10. End Session (Logout)
GET /api/v1/oidc/end-session
OpenID Connect RP-Initiated Logout 1.0. Ends the user's session at the provider.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
id_token_hint | string | Recommended | The ID token received during authentication |
post_logout_redirect_uri | string | No | URL to redirect to after logout. Must share origin with a registered redirect URI. |
state | string | No | Opaque value passed back to post_logout_redirect_uri |
client_id | string | No | Client identifier (used if id_token_hint is not provided) |
Example:
GET /api/v1/oidc/end-session
?id_token_hint=eyJhbGciOiJSUzI1NiIs...
&post_logout_redirect_uri=https://partners.calimatic.com/logged-out
&state=abc123
Behavior:
- Validates
id_token_hint(extracts user and client) - Validates
post_logout_redirect_uriagainst registered origins - Revokes all refresh tokens for user + client
- Redirects to the platform's signout page (clears session)
- After signout, redirects to
post_logout_redirect_uri?state=abc123
11. Scopes & Claims
Standard Scopes
| Scope | Description | Claims Granted |
|---|---|---|
openid | Required. Verify user identity | sub |
profile | Name and profile picture | name, given_name, family_name, picture, updated_at |
email | Email address | email, email_verified |
phone | Phone number | phone_number |
offline_access | Issue a refresh token | (none — enables refresh token in response) |
Calimatic-Specific Scopes
| Scope | Description | Claims Granted |
|---|---|---|
organization | Organization context | organization_id, organization_memberships, user_type |
permissions | Platform permissions | permissions, roles |
Claim Definitions
| Claim | Type | Description |
|---|---|---|
sub | string | Keycloak user ID (stable, unique per user) |
name | string | Display name |
given_name | string | First name |
family_name | string | Last name |
email | string | Email address |
email_verified | boolean | Whether email is verified |
picture | string | Avatar URL |
phone_number | string | Phone number |
updated_at | number | Unix timestamp of last profile update |
organization_id | string | Primary organization UUID |
organization_memberships | array | [{ organization_id, role }] |
user_type | string | customer_admin, customer_teacher, student, parent, etc. |
permissions | string[] | All resolved platform permissions |
roles | string[] | Assigned role names |
Access Token JWT Claims
Access tokens always include these standard JWT claims plus scope-dependent user claims:
{
"iss": "https://auth.calimatic.com",
"sub": "keycloak-user-id",
"aud": "cca_clientId...",
"iat": 1699996400,
"exp": 1700000000,
"scope": "openid profile email organization",
"token_type": "access_token",
"email": "jane@example.com",
"email_verified": true,
"name": "Jane Smith",
"given_name": "Jane",
"family_name": "Smith",
"picture": "https://...",
"organization_id": "org-uuid",
"organization_memberships": [...],
"user_type": "customer_admin"
}
ID Token JWT Claims
{
"iss": "https://auth.calimatic.com",
"sub": "keycloak-user-id",
"aud": "cca_clientId...",
"iat": 1699996400,
"exp": 1700000000,
"nonce": "n-0S6_WzA2Mj",
"token_type": "id_token",
"email": "jane@example.com",
"email_verified": true,
"name": "Jane Smith",
"given_name": "Jane",
"family_name": "Smith"
}
12. Error Responses
All error responses follow the OAuth 2.0 error format.
Token / Introspect / Revoke Endpoint Errors (JSON)
{
"error": "invalid_grant",
"error_description": "Authorization code is invalid, expired, or already used"
}
HTTP status codes:
| Status | When |
|---|---|
| 400 | Invalid request parameters, invalid grant, invalid scope |
| 401 | Invalid client credentials |
| 405 | Wrong HTTP method |
| 429 | Rate limit exceeded |
| 500 | Server error |
Authorization Endpoint Errors (Redirect)
Errors are returned as query parameters on the redirect_uri:
https://partners.calimatic.com/callback?error=access_denied&error_description=...&state=xyz
Errors before redirect_uri validation return JSON directly (400 status).
UserInfo Endpoint Errors (Bearer)
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="Token is invalid or expired"
Error Codes
| Code | Description |
|---|---|
invalid_request | Missing or invalid parameter |
invalid_client | Client authentication failed |
invalid_grant | Code/token invalid, expired, or already used |
unauthorized_client | Client not authorized for this grant type |
unsupported_grant_type | Grant type not supported |
unsupported_response_type | Response type not supported |
invalid_scope | Requested scope not supported or not allowed |
access_denied | User denied the authorization request |
server_error | Internal server error |
temporarily_unavailable | Rate limited or service overloaded |
invalid_token | Bearer token invalid or expired |
login_required | User must authenticate |
consent_required | User must grant consent |
13. Rate Limits
| Endpoint | Limit | Window | Key |
|---|---|---|---|
/api/v1/oidc/token | 20 requests | 1 minute | Per client |
/api/v1/oidc/authorize | 30 requests | 1 minute | Per IP |
/api/v1/oidc/introspect | 60 requests | 1 minute | Per client |
/api/v1/oidc/revoke | 20 requests | 1 minute | Per client |
/api/v1/oidc/userinfo | 60 requests | 1 minute | Per token |
/api/v1/oidc/register | 5 requests | 1 minute | Per IP |
Rate limit response (429):
{
"error": "temporarily_unavailable",
"error_description": "Rate limit exceeded"
}
Headers: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining.
14. Token Lifetimes
| Token | Default TTL | Configurable |
|---|---|---|
| Authorization Code | 60 seconds | No |
| Access Token | 3600 seconds (1 hour) | Per client (access_token_ttl) |
| ID Token | Same as access token | Per client |
| Refresh Token | 86400 seconds (24 hours) | Per client (refresh_token_ttl) |
15. Security Considerations
PKCE (Proof Key for Code Exchange)
PKCE is required by default for all clients. Use the S256 challenge method:
code_verifier = random 43-128 character string (unreserved URI characters)
code_challenge = BASE64URL(SHA256(code_verifier))
State Parameter
Always include a cryptographically random state parameter in authorization requests to prevent CSRF attacks. Verify it matches when receiving the callback.
Token Storage
- Confidential clients (server-side): Store
client_secretand refresh tokens securely (environment variables, secrets manager). Never expose in client-side code. - Public clients (SPAs): Never store refresh tokens in localStorage. Use httpOnly cookies or in-memory storage. Always use PKCE.
- Access tokens: Short-lived by design. Store in memory when possible.
Redirect URI Validation
Redirect URIs are validated with exact match against registered URIs. No wildcards are supported. Register all environments (production, staging, localhost for development).
CORS
All OIDC endpoints and /.well-known/* return CORS headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
JWT Verification
Always verify JWTs using the public keys from the JWKS endpoint:
- Fetch JWKS from
/api/v1/oidc/jwks(cache for 15 minutes) - Match the
kidfrom the JWT header to a key in the JWKS - Verify the signature using the matched RSA public key
- Validate
issequalshttps://auth.calimatic.com - Validate
audmatches yourclient_id - Validate
expis in the future