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.sqlTables created:
oidc_clients- OAuth2/OIDC client applicationsoidc_auth_requests- Authorization requests (temporary)oidc_refresh_tokens- Refresh tokensoidc_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 | postgresStep 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 daysGenerate crypto key:
openssl rand -base64 32Step 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 bootstrapVerify 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:00Step 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.sqlStep 5: Test OIDC Flow
Test the complete OIDC authorization code flow:
Manual Testing
- 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- Login as User:
You’ll be redirected to the login page. Login with your credentials.
- Get Authorization Code:
After successful login, you’ll be redirected to:
http://localhost:3000/callback?code=AUTH_CODE_HERE&state=random-state-123- 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"
}- 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.shStep 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 only2. 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=2004. Configure CORS
OIDC_ALLOWED_ORIGINS=https://app.your-domain.com,https://admin.your-domain.com5. Backup Signing Keys
# Backup encrypted keys
pg_dump -d iota_erp -t oidc_signing_keys > oidc_keys_backup.sqlRollback 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.sql3. Remove Environment Variables
Remove OIDC-related variables from .env.
4. Restart Application
make restartCommon 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 restartIssue: 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_KEYmatches the key used during key generation - Check environment variable is loaded correctly
- Re-bootstrap keys with correct crypto key
Next Steps
After successful migration:
- Review API Reference for endpoint details
- Review Configuration Guide for advanced settings
- Implement token refresh in your application
- Setup monitoring and alerting
- Conduct security audit