Docs/OIDC + PKCE Integration

OIDC + PKCE Integration

Practical guidance for integrating clients and enforcing secure access.

OIDC + PKCE Integration

This guide shows how to integrate a public client (SPA/mobile/server-rendered web) with the Fulan Auth Service using an OAuth2/OIDC-style flow:

  • OIDC Discovery + JWKS (for standard client configuration and JWT verification)
  • Authorization Code + PKCE (recommended for public clients)
  • Optional OTP identifier verification (policy-driven per app)

Quick checklist

Before starting:

  • You have an app client_id created in the Portal.
  • Your redirect_uri is registered exactly (string match).
  • You know the Auth Service base URL (examples below assume http://localhost:8080).

Endpoints you will use

PurposeEndpoint
OIDC discoveryGET /.well-known/openid-configuration
JWKSGET /.well-known/jwks.json
Start authorizationGET /authorize
Exchange code for tokensPOST /token
Send OTP (if required)POST /otp/send
Verify OTP (if required)POST /otp/verify

1) Discovery + JWKS

Discovery helps your client find URLs like the authorization endpoint and token endpoint.

curl -sS http://localhost:8080/.well-known/openid-configuration

JWKS is used to validate JWT signatures (select the key by kid).

curl -sS http://localhost:8080/.well-known/jwks.json

2) Generate PKCE parameters

You will generate:

  • code_verifier: high-entropy random string (kept client-side)
  • code_challenge: base64url(SHA256(code_verifier))

Example (bash + openssl):

code_verifier="$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=' | tr -d '\n')"
code_challenge="$(printf '%s' "$code_verifier" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')"
echo "code_verifier=$code_verifier"
echo "code_challenge=$code_challenge"

3) (If required) verify identifier via OTP

Some apps require a verified identifier before tokens are issued. If the app is configured with requireVerifiedIdentifiers=true, do this step before starting /authorize.

Send OTP:

curl -sS -X POST http://localhost:8080/otp/send \
  -H 'content-type: application/json' \
  -d '{"client_id":"<client_id>","identifier_type":"email","identifier_value":"user@example.com"}'

Verify OTP:

curl -sS -X POST http://localhost:8080/otp/verify \
  -H 'content-type: application/json' \
  -d '{"client_id":"<client_id>","otp_id":"<otp_id>","code":"<code>"}'

4) Request an authorization code

Call /authorize with these parameters:

  • response_type=code
  • client_id
  • redirect_uri (must match a registered redirect URI exactly)
  • scope (space-separated; must be allowed by the app scope allowlist)
  • code_challenge and code_challenge_method=S256
  • Recommended: state (random string; protect against CSRF)
  • If requireVerifiedIdentifiers=true, include identifier_type and identifier_value

Example:

GET http://localhost:8080/authorize?response_type=code&client_id=<client_id>&redirect_uri=<redirect_uri>&scope=openid&code_challenge=<code_challenge>&code_challenge_method=S256&state=<state>&identifier_type=email&identifier_value=user@example.com

On success, the service redirects (302) to:

<redirect_uri>?code=<auth_code>&state=<state>

Verify that the returned state matches what you sent.

5) Exchange the code for tokens

Use the saved code_verifier from step 2.

curl -sS -X POST http://localhost:8080/token \
  -H 'content-type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'client_id=<client_id>' \
  --data-urlencode 'code=<auth_code>' \
  --data-urlencode 'redirect_uri=<redirect_uri>' \
  --data-urlencode 'code_verifier=<code_verifier>'

The response contains at least:

  • access_token
  • refresh_token

Token validation (access_token)

When validating the access token:

  1. Verify signature using JWKS (kid in header selects the key).
  2. Validate iss is exactly the expected issuer (example: http://localhost:8080).
  3. Validate aud equals the app client_id.
  4. Validate exp is in the future.
  5. Use the scope claim for authorization decisions (split by spaces).

Common pitfalls

  • Redirect issues: redirect_uri must be an exact match to the registered URI (no extra slashes, different ports, or missing path).
  • Scope rejected: requested scopes must be in the app’s allowlist.
  • PKCE mismatch: use the same code_verifier that generated code_challenge.
  • OTP required: if requireVerifiedIdentifiers=true, you must verify before /authorize succeeds.