Skip to Content
OpenID Connect (OIDC)Migration Guide

OIDC Migration Guide

This guide explains how to enable and configure OIDC on an existing IOTA SDK installation.

Prerequisites

  • IOTA SDK installed and running
  • PostgreSQL 17+ database
  • Go 1.23.2 or higher
  • Access to database migration tools

Step 1: Run Database Migrations

The OIDC module requires several database tables. Run migrations to create them:

# Apply all migrations (includes OIDC tables) make db migrate up # Or apply specific OIDC migration sql-migrate up -limit=1 migrations/XXXXXX_create_oidc_tables.sql

Tables created:

  • oidc_clients - OAuth2/OIDC client applications
  • oidc_auth_requests - Authorization requests (temporary)
  • oidc_refresh_tokens - Refresh tokens
  • oidc_signing_keys - RSA signing keys (encrypted)

Verify migrations:

psql -d iota_erp -c "\dt oidc_*"

Expected output:

List of relations Schema | Name | Type | Owner --------+----------------------+-------+---------- public | oidc_auth_requests | table | postgres public | oidc_clients | table | postgres public | oidc_refresh_tokens | table | postgres public | oidc_signing_keys | table | postgres

Step 2: Configure Environment Variables

Add OIDC configuration to your .env file:

# Required: Crypto key for encrypting signing keys OIDC_CRYPTO_KEY=your-secure-random-32-char-minimum-key-here # Required: Issuer URL (must match your public domain) OIDC_ISSUER_URL=https://your-domain.com # Optional: Token lifetimes (defaults shown) OIDC_ACCESS_TOKEN_LIFETIME=3600 # 1 hour OIDC_ID_TOKEN_LIFETIME=3600 # 1 hour OIDC_REFRESH_TOKEN_LIFETIME=2592000 # 30 days

Generate crypto key:

openssl rand -base64 32

Step 3: Bootstrap Signing Keys

The OIDC module uses RSA keys to sign tokens. Bootstrap the initial keypair:

# Start your application (keys will be auto-generated on first run) make run # Or manually trigger key generation psql -d iota_erp -c "SELECT COUNT(*) FROM oidc_signing_keys;" # If count is 0, restart the application to trigger bootstrap

Verify keys were created:

psql -d iota_erp -c "SELECT key_id, algorithm, is_active, created_at FROM oidc_signing_keys;"

Expected output:

key_id | algorithm | is_active | created_at -----------------------------------+-----------+-----------+------------------------- 550e8400-e29b-41d4-a716-446655440000 | RS256 | t | 2026-01-31 12:00:00

Step 4: Register Your First Client

Create an OAuth2/OIDC client for your application:

Option 1: Using SQL

INSERT INTO oidc_clients ( client_id, name, application_type, redirect_uris, grant_types, response_types, scopes, require_pkce, is_active ) VALUES ( 'my-first-client', 'My Application', 'web', ARRAY['http://localhost:3000/callback'], ARRAY['authorization_code', 'refresh_token'], ARRAY['code'], ARRAY['openid', 'profile', 'email', 'offline_access'], false, -- Set to true for production true );

Option 2: Using Go Code

package main import ( "context" "github.com/iota-uz/iota-sdk/modules/oidc/domain/entities/client" "github.com/iota-uz/iota-sdk/modules/oidc/infrastructure/persistence" ) func main() { ctx := context.Background() // Create client repository clientRepo := persistence.NewClientRepository() // Create new client newClient := client.New( "my-first-client", "My Application", "web", []string{"http://localhost:3000/callback"}, client.WithGrantTypes([]string{"authorization_code", "refresh_token"}), client.WithScopes([]string{"openid", "profile", "email", "offline_access"}), client.WithRequirePKCE(true), ) // Save to database createdClient, err := clientRepo.Create(ctx, newClient) if err != nil { panic(err) } fmt.Printf("Client created: %s\n", createdClient.ClientID()) }

Option 3: Using Example Script

psql -d iota_erp -f modules/oidc/examples/client_setup.sql

Step 5: Test OIDC Flow

Test the complete OIDC authorization code flow:

Manual Testing

  1. Start Authorization Flow:

Generate PKCE values:

CODE_VERIFIER=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=' | cut -c1-43) CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | shasum -a 256 | awk '{print $1}' | xxd -r -p | base64 | tr '+/' '-_' | tr -d '=' | tr -d "\n")

Visit in your browser:

http://localhost:8080/oidc/authorize?client_id=my-first-client&redirect_uri=http://localhost:3000/callback&response_type=code&scope=openid%20profile%20email&state=random-state-123&code_challenge=CODE_CHALLENGE&code_challenge_method=S256
  1. Login as User:

You’ll be redirected to the login page. Login with your credentials.

  1. Get Authorization Code:

After successful login, you’ll be redirected to:

http://localhost:3000/callback?code=AUTH_CODE_HERE&state=random-state-123
  1. Exchange Code for Tokens:
curl -X POST http://localhost:8080/oidc/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "client_id=my-first-client" \ -d "code=AUTH_CODE_HERE" \ -d "redirect_uri=http://localhost:3000/callback" \ -d "code_verifier=$CODE_VERIFIER"

Expected response:

{ "access_token": "<ACCESS_TOKEN>", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "<REFRESH_TOKEN>", "id_token": "<ID_TOKEN>", "scope": "openid profile email" }
  1. Get User Info:
curl -X GET http://localhost:8080/oidc/userinfo \ -H "Authorization: Bearer <ACCESS_TOKEN>"

Automated Testing

Use the provided shell script:

bash modules/oidc/examples/curl_flow.sh

Step 6: Update Your Application

Frontend Integration

Example (JavaScript):

// Authorization const authorizeUrl = new URL('http://localhost:8080/oidc/authorize'); const codeVerifier = generateCodeVerifier(); const codeChallenge = await generateCodeChallenge(codeVerifier); sessionStorage.setItem('pkce_verifier', codeVerifier); authorizeUrl.searchParams.set('client_id', 'my-first-client'); authorizeUrl.searchParams.set('redirect_uri', 'http://localhost:3000/callback'); authorizeUrl.searchParams.set('response_type', 'code'); authorizeUrl.searchParams.set('scope', 'openid profile email'); authorizeUrl.searchParams.set('state', generateRandomState()); authorizeUrl.searchParams.set('code_challenge', codeChallenge); authorizeUrl.searchParams.set('code_challenge_method', 'S256'); window.location.href = authorizeUrl.toString(); // Handle callback const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); const state = urlParams.get('state'); const storedCodeVerifier = sessionStorage.getItem('pkce_verifier'); // Exchange code for tokens (should be done server-side) fetch('http://localhost:8080/oidc/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', client_id: 'my-first-client', code: code, redirect_uri: 'http://localhost:3000/callback', code_verifier: storedCodeVerifier }) });

Backend Integration

Example (Go):

package main import ( "net/http" "github.com/gorilla/sessions" ) func handleCallback(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") state := r.URL.Query().Get("state") // Verify state matches session // Exchange code for tokens tokens, err := exchangeCodeForTokens(code) if err != nil { http.Error(w, "Token exchange failed", http.StatusInternalServerError) return } // Store tokens in session session.Values["access_token"] = tokens.AccessToken session.Values["refresh_token"] = tokens.RefreshToken session.Save(r, w) http.Redirect(w, r, "/dashboard", http.StatusFound) }

Step 7: Production Deployment

Before deploying to production:

1. Update Environment

# Production .env OIDC_ISSUER_URL=https://your-production-domain.com OIDC_CRYPTO_KEY=<new-secure-key-from-secret-manager> # Use HTTPS redirect URIs only

2. Update Client Redirect URIs

UPDATE oidc_clients SET redirect_uris = ARRAY['https://app.your-domain.com/callback'] WHERE client_id = 'my-first-client';

3. Enable Rate Limiting

# In .env OIDC_RATE_LIMIT_ENABLED=true OIDC_RATE_LIMIT_AUTHORIZE=100 OIDC_RATE_LIMIT_TOKEN=50 OIDC_RATE_LIMIT_USERINFO=200

4. Configure CORS

OIDC_ALLOWED_ORIGINS=https://app.your-domain.com,https://admin.your-domain.com

5. Backup Signing Keys

# Backup encrypted keys pg_dump -d iota_erp -t oidc_signing_keys > oidc_keys_backup.sql

Rollback Procedure

If you need to rollback the OIDC migration:

1. Disable OIDC Routes

Comment out OIDC routes in your router configuration.

2. Rollback Database

# Rollback OIDC migration sql-migrate down -limit=1 migrations/XXXXXX_create_oidc_tables.sql

3. Remove Environment Variables

Remove OIDC-related variables from .env.

4. Restart Application

make restart

Common Migration Issues

Issue: Signing Keys Not Generated

Symptom: SELECT COUNT(*) FROM oidc_signing_keys returns 0

Solution:

# Manually trigger bootstrap go run scripts/bootstrap_oidc_keys.go # Or restart application make restart

Issue: Client Creation Fails

Symptom: pq: duplicate key value violates unique constraint "oidc_clients_client_id_key"

Solution:

-- Check if client already exists SELECT client_id, name FROM oidc_clients WHERE client_id = 'my-first-client'; -- Delete existing client if needed DELETE FROM oidc_clients WHERE client_id = 'my-first-client';

Issue: Invalid Redirect URI

Symptom: invalid_redirect_uri error during authorization

Solution:

-- Verify redirect URIs SELECT client_id, redirect_uris FROM oidc_clients WHERE client_id = 'my-first-client'; -- Update redirect URIs (exact match required) UPDATE oidc_clients SET redirect_uris = ARRAY['http://localhost:3000/callback'] WHERE client_id = 'my-first-client';

Issue: Token Decryption Fails

Symptom: failed to decrypt private key

Solution:

  • Verify OIDC_CRYPTO_KEY matches the key used during key generation
  • Check environment variable is loaded correctly
  • Re-bootstrap keys with correct crypto key

Next Steps

After successful migration:

  1. Review API Reference for endpoint details
  2. Review Configuration Guide for advanced settings
  3. Implement token refresh in your application
  4. Setup monitoring and alerting
  5. Conduct security audit
Last updated on