Files
2026-01-08 15:59:53 +10:30

15 KiB

OIDC Testing Guide

This guide provides step-by-step instructions for manually testing the OIDC implementation using curl commands.

Prerequisites

  1. A running instance of the authy2 backend
  2. curl installed
  3. A test user account
  4. A registered OIDC client

Setup

Start the Backend

# Development mode
python -m flask run --host=0.0.0.0 --port=5000

# Or using the manage.py script
python manage.py runserver --host=0.0.0.0 --port=5000

Register a Test User (if needed)

curl -X POST http://localhost:5000/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "TestPassword123!",
    "password_confirm": "TestPassword123!",
    "full_name": "Test User"
  }'

Register an OIDC Client

# Register a new OIDC client
curl -X POST http://localhost:5000/oidc/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "Test Client",
    "redirect_uris": ["http://localhost:8080/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "scope": "openid profile email",
    "token_endpoint_auth_method": "client_secret_basic"
  }'

Save the client_id and client_secret from the response for later use.

Testing Endpoints

1. Discovery Endpoint

Purpose: Verify OIDC discovery configuration is accessible and correct.

curl -s http://localhost:5000/.well-known/openid-configuration | jq

Expected Response:

{
  "issuer": "http://localhost:5000",
  "authorization_endpoint": "http://localhost:5000/oidc/authorize",
  "token_endpoint": "http://localhost:5000/oidc/token",
  "userinfo_endpoint": "http://localhost:5000/oidc/userinfo",
  "jwks_uri": "http://localhost:5000/oidc/jwks",
  "registration_endpoint": "http://localhost:5000/oidc/register",
  "revocation_endpoint": "http://localhost:5000/oidc/revoke",
  "introspection_endpoint": "http://localhost:5000/oidc/introspect",
  "scopes_supported": ["openid", "profile", "email"],
  "response_types_supported": ["code"],
  "response_modes_supported": ["query"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "claims_supported": ["sub", "name", "email", "email_verified"]
}

Verification:

  • All endpoints are present and use the correct base URL
  • Cache-Control header is set: curl -I http://localhost:5000/.well-known/openid-configuration

2. JWKS Endpoint

Purpose: Verify JWKS is accessible and contains valid signing keys.

curl -s http://localhost:5000/oidc/jwks | jq

Expected Response:

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "...",
      "use": "sig",
      "alg": "RS256",
      "n": "...",
      "e": "..."
    }
  ]
}

Verification:

  • At least one key is present
  • Key has kty: "RSA", alg: "RS256"
  • Cache-Control header is set

3. Authorization Code Flow with PKCE

This is the complete OAuth2/OIDC authentication flow.

Step 1: Generate PKCE Parameters

# Generate code verifier (43-128 characters)
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=' | tr '/+' '_-' | cut -c1-43)

# Generate code challenge from verifier
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl sha256 -binary | base64 | tr -d '=' | tr '/+' '_-')

# Generate state parameter
STATE=$(openssl rand -hex 16)

# Generate nonce for ID token
NONCE=$(openssl rand -hex 16)

echo "Code Verifier: $CODE_VERIFIER"
echo "Code Challenge: $CODE_CHALLENGE"
echo "State: $STATE"
echo "Nonce: $NONCE"

Step 2: Request Authorization Code

Option A: Browser-based flow (redirect flow)

# Open this URL in a browser
http://localhost:5000/oidc/authorize?\
  client_id=YOUR_CLIENT_ID&\
  redirect_uri=http://localhost:8080/callback&\
  response_type=code&\
  scope=openid%20profile%20email&\
  state=YOUR_STATE&\
  nonce=YOUR_NONCE&\
  code_challenge=YOUR_CODE_CHALLENGE&\
  code_challenge_method=S256

Option B: POST-based flow (for testing with curl)

curl -v -X POST http://localhost:5000/oidc/authorize \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "redirect_uri=http://localhost:8080/callback" \
  -d "response_type=code" \
  -d "scope=openid profile email" \
  -d "state=$STATE" \
  -d "nonce=$NONCE" \
  -d "code_challenge=$CODE_CHALLENGE" \
  -d "code_challenge_method=S256" \
  -d "email=test@example.com" \
  -d "password=TestPassword123!"

Expected Response: 302 Redirect with code parameter

HTTP/1.1 302 Found
Location: http://localhost:8080/callback?code=AUTHORIZATION_CODE&state=YOUR_STATE

Extract the authorization code:

# From the Location header
AUTH_CODE=$(curl -v -X POST http://localhost:5000/oidc/authorize \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "redirect_uri=http://localhost:8080/callback" \
  -d "response_type=code" \
  -d "scope=openid profile email" \
  -d "state=$STATE" \
  -d "nonce=$NONCE" \
  -d "code_challenge=$CODE_CHALLENGE" \
  -d "code_challenge_method=S256" \
  -d "email=test@example.com" \
  -d "password=TestPassword123!" 2>&1 | grep -i "Location:" | cut -d' ' -f2 | cut -d'?' -f2 | cut -d'=' -f2)

Step 3: Exchange Authorization Code for Tokens

# Using client_id and client_secret
curl -X POST http://localhost:5000/oidc/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=$AUTH_CODE" \
  -d "redirect_uri=http://localhost:8080/callback" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "code_verifier=$CODE_VERIFIER"

Expected Response:

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "message": "Tokens issued successfully",
  "request_id": "...",
  "data": {
    "access_token": "eyJ...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "id_token": "eyJ...",
    "refresh_token": "..."
  }
}

Verification:

  • access_token is a JWT (check at jwt.io)
  • token_type is "Bearer"
  • expires_in indicates token lifetime
  • id_token contains expected claims (sub, iss, aud, etc.)

4. UserInfo Endpoint

Purpose: Retrieve user information using the access token.

curl -X GET http://localhost:5000/oidc/userinfo \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Expected Response:

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "message": "User info retrieved successfully",
  "request_id": "...",
  "data": {
    "sub": "user-id",
    "name": "Test User",
    "email": "test@example.com",
    "email_verified": true
  }
}

Verification:

  • sub matches the user ID
  • email and email_verified are present if email scope was requested
  • name is present if profile scope was requested

5. Token Refresh

Purpose: Obtain a new access token using a refresh token.

curl -X POST http://localhost:5000/oidc/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=YOUR_REFRESH_TOKEN" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

Expected Response:

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "message": "Tokens refreshed successfully",
  "request_id": "...",
  "data": {
    "access_token": "eyJ...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "id_token": "eyJ...",
    "refresh_token": "..."
  }
}

Verification:

  • New access_token is returned
  • New refresh_token is returned (token rotation)
  • Old refresh token is now invalid

6. Token Revocation

Purpose: Revoke a token to invalidate it.

# Revoke access token
curl -X POST http://localhost:5000/oidc/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=YOUR_ACCESS_TOKEN" \
  -d "token_type_hint=access_token" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

# Revoke refresh token
curl -X POST http://localhost:5000/oidc/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=YOUR_REFRESH_TOKEN" \
  -d "token_type_hint=refresh_token" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

Expected Response:

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "message": "Token revoked successfully",
  "request_id": "..."
}

Verification:

  • Revoked refresh token cannot be used for refresh
  • Revoked access token cannot be used for UserInfo

7. Token Introspection

Purpose: Check if a token is active and get its claims.

curl -X POST http://localhost:5000/oidc/introspect \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=YOUR_ACCESS_TOKEN" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

Expected Response (active token):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "message": "Token introspection successful",
  "request_id": "...",
  "data": {
    "active": true,
    "iss": "http://localhost:5000",
    "sub": "user-id",
    "aud": "YOUR_CLIENT_ID",
    "exp": 1234567890,
    "iat": 1234564290,
    "scope": "openid profile email",
    "token_type": "Bearer"
  }
}

Expected Response (invalid/expired token):

{
  "version": "1.0",
  "success": true,
  "code": 200,
  "message": "Token introspection successful",
  "request_id": "...",
  "data": {
    "active": false
  }
}

Complete Flow Test Script

Here's a comprehensive script that tests the complete OIDC flow:

#!/bin/bash
set -e

BASE_URL="http://localhost:5000"
CLIENT_ID="YOUR_CLIENT_ID"
CLIENT_SECRET="YOUR_CLIENT_SECRET"
EMAIL="test@example.com"
PASSWORD="TestPassword123!"
REDIRECT_URI="http://localhost:8080/callback"

echo "=== OIDC Complete Flow Test ==="

# 1. Discovery
echo -e "\n1. Testing Discovery Endpoint..."
curl -s "$BASE_URL/.well-known/openid-configuration" | jq . > /dev/null
echo "   ✓ Discovery endpoint working"

# 2. JWKS
echo -e "\n2. Testing JWKS Endpoint..."
curl -s "$BASE_URL/oidc/jwks" | jq . > /dev/null
echo "   ✓ JWKS endpoint working"

# 3. Generate PKCE parameters
echo -e "\n3. Generating PKCE parameters..."
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=' | tr '/+' '_-' | cut -c1-43)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl sha256 -binary | base64 | tr -d '=' | tr '/+' '_-')
STATE=$(openssl rand -hex 16)
echo "   ✓ PKCE parameters generated"

# 4. Get Authorization Code
echo -e "\n4. Getting Authorization Code..."
AUTH_RESPONSE=$(curl -s -D - -X POST "$BASE_URL/oidc/authorize" \
  -d "client_id=$CLIENT_ID" \
  -d "redirect_uri=$REDIRECT_URI" \
  -d "response_type=code" \
  -d "scope=openid profile email" \
  -d "state=$STATE" \
  -d "code_challenge=$CODE_CHALLENGE" \
  -d "code_challenge_method=S256" \
  -d "email=$EMAIL" \
  -d "password=$PASSWORD")

AUTH_CODE=$(echo "$AUTH_RESPONSE" | grep -i "Location:" | cut -d'?' -f2 | cut -d'=' -f2 | tr -d '\r')
echo "   ✓ Authorization code received: ${AUTH_CODE:0:20}..."

# 5. Exchange Code for Tokens
echo -e "\n5. Exchanging Code for Tokens..."
TOKEN_RESPONSE=$(curl -s -X POST "$BASE_URL/oidc/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=$AUTH_CODE" \
  -d "redirect_uri=$REDIRECT_URI" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" \
  -d "code_verifier=$CODE_VERIFIER")

ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.data.access_token')
REFRESH_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.data.refresh_token')
echo "   ✓ Tokens received"

# 6. UserInfo
echo -e "\n6. Testing UserInfo Endpoint..."
USERINFO=$(curl -s -X GET "$BASE_URL/oidc/userinfo" \
  -H "Authorization: Bearer $ACCESS_TOKEN")
echo "   ✓ UserInfo response: $(echo "$USERINFO" | jq -r '.data.sub')"

# 7. Token Refresh
echo -e "\n7. Testing Token Refresh..."
REFRESH_RESPONSE=$(curl -s -X POST "$BASE_URL/oidc/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=$REFRESH_TOKEN" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET")

NEW_ACCESS_TOKEN=$(echo "$REFRESH_RESPONSE" | jq -r '.data.access_token')
NEW_REFRESH_TOKEN=$(echo "$REFRESH_RESPONSE" | jq -r '.data.refresh_token')
echo "   ✓ Token refresh successful"

# 8. Token Introspection
echo -e "\n8. Testing Token Introspection..."
INTROSPECT=$(curl -s -X POST "$BASE_URL/oidc/introspect" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=$NEW_ACCESS_TOKEN" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET")
IS_ACTIVE=$(echo "$INTROSPECT" | jq -r '.data.active')
echo "   ✓ Token introspection: active=$IS_ACTIVE"

# 9. Token Revocation
echo -e "\n9. Testing Token Revocation..."
curl -s -X POST "$BASE_URL/oidc/revoke" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=$NEW_REFRESH_TOKEN" \
  -d "token_type_hint=refresh_token" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" > /dev/null
echo "   ✓ Token revoked"

# 10. Verify Revoked Token
echo -e "\n10. Verifying Revoked Token..."
REVOKE_VERIFY=$(curl -s -X POST "$BASE_URL/oidc/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=$NEW_REFRESH_TOKEN" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET")
IS_INVALID=$(echo "$REVOKE_VERIFY" | jq -r '.success')
echo "   ✓ Revoked token is invalid: success=$IS_INVALID"

echo -e "\n=== OIDC Flow Test Complete ==="
echo "All endpoints tested successfully!"

Error Handling Tests

Invalid Client

curl -X POST http://localhost:5000/oidc/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=invalid" \
  -d "client_id=invalid_client" \
  -d "client_secret=invalid_secret"

Invalid Authorization Code

curl -X POST http://localhost:5000/oidc/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=INVALID_CODE" \
  -d "redirect_uri=http://localhost:8080/callback" \
  -d "client_id=YOUR_CLIENT_ID"

Expired Authorization Code

Authorization codes expire after 10 minutes. Wait 10+ minutes and try to use the code again.

Invalid PKCE Verifier

Use an incorrect code_verifier during token exchange:

curl -X POST http://localhost:5000/oidc/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=YOUR_AUTH_CODE" \
  -d "redirect_uri=http://localhost:8080/callback" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "code_verifier=wrong_verifier"

Troubleshooting

Connection Refused

Ensure the backend is running:

ps aux | grep flask
lsof -i :5000

Authentication Failures

  1. Verify user credentials are correct
  2. Check that the user exists in the database
  3. Ensure the client is active and has correct redirect URIs

Token Errors

  1. Verify access token hasn't expired
  2. Check that the token was signed by the OIDC provider
  3. Ensure the audience (client_id) matches

Redirect URI Mismatch

Ensure the redirect_uri used in authorization and token exchange exactly matches a registered redirect URI.