Skip to content

Developer SSO Reference

This is the integration index for third-party applications using SSO Timeh as their OpenID Provider. Start with discovery and do not hardcode endpoints beyond the initial bootstrap.

Production issuer: https://api-sso.timeh.my.id

Authoritative discovery: https://api-sso.timeh.my.id/.well-known/openid-configuration

NeedDocument
Client registration and public versus confidential selectionClient Web App Onboarding
OAuth/OIDC endpoint contractsAPI Reference
Scopes, claims, UserInfo, and token rolesScopes and Claims
OAuth errors and support referencesErrors and FAQ
Mandatory PKCE, token TTLs, rotation, and JWKSSecurity Model
Access token validation for APIsResource Server Guide
Laravel confidential clientLaravel Integration
Next.js route-handler BFFNext.js Integration
Vue.js public SPAVue.js Integration
Express confidential clientExpress Integration

Authorization Code + PKCE Flow

Required Knowledge

  1. openid is required on every /authorize request.
  2. PKCE S256 is mandatory for every client, including confidential clients.
  3. state and nonce are mandatory and must be validated at callback.
  4. Access tokens are ES256 JWTs with aud = sso-resource-api. Never use an ID token as an API credential.
  5. A refresh token is issued only when offline_access is requested and allowed.
  6. Public clients can call the token endpoint without a secret, but PKCE remains mandatory.

The first-party portal and admin panel use the confidential BFF + PKCE S256 pattern. Their secrets and tokens remain in server runtime; browsers receive same-origin session cookies only.

Python Skeleton with Authlib

python
from authlib.integrations.requests_client import OAuth2Session
from authlib.common.security import generate_token
import base64
import hashlib

issuer = "https://api-sso.timeh.my.id"
client_id = "your-client-id"
redirect_uri = "https://app.example.com/auth/callback"

code_verifier = generate_token(64)
challenge = hashlib.sha256(code_verifier.encode("ascii")).digest()
code_challenge = base64.urlsafe_b64encode(challenge).rstrip(b"=").decode("ascii")

state = generate_token(32)
nonce = generate_token(32)

client = OAuth2Session(client_id=client_id, redirect_uri=redirect_uri, scope="openid profile email")
authorization_url, state = client.create_authorization_url(
    f"{issuer}/authorize",
    state=state,
    nonce=nonce,
    code_challenge=code_challenge,
    code_challenge_method="S256",
)

token = client.fetch_token(
    f"{issuer}/token",
    code="code-from-callback",
    code_verifier=code_verifier,
    client_id=client_id,
)

OAuth libraries that cannot produce a verifier and code_challenge_method=S256 are incompatible with this provider.

Released under the MIT License.