25 KiB
Rodauth-OAuth Analysis: Comprehensive Comparison with Clinch's Custom Implementation
Executive Summary
Rodauth-OAuth is a production-ready Ruby gem that implements the OAuth 2.0 framework and OpenID Connect on top of the rodauth authentication library. It's architected as a modular feature-based system that integrates with Roda (a routing library) and provides extensive OAuth/OIDC capabilities.
Your current Clinch implementation is a custom, minimalist Rails-based OIDC provider focusing on the authorization code grant with PKCE support. Switching to rodauth-oauth would provide significantly more features and standards compliance but requires architectural changes.
1. What Rodauth-OAuth Is
Core Identity
- Type: Ruby gem providing OAuth 2.0 & OpenID Connect implementation
- Framework: Built on top of
rodauth(a dedicated authentication library) - Web Framework: Designed for Roda framework (lightweight, routing-focused)
- Rails Support: Available via
rodauth-railswrapper - Maturity: Production-ready, OpenID-Certified for multiple profiles
- Author: Tiago Cardoso (tiago.cardoso@gmail.com)
- License: Apache 2.0
Architecture Philosophy
- Feature-based: Modular "features" that can be enabled/disabled
- Database-agnostic: Uses Sequel ORM, works with any SQL database
- Highly configurable: Override methods to customize behavior
- Standards-focused: Implements RFCs and OpenID specs strictly
2. File Structure and Organization
Directory Layout in /tmp/rodauth-oauth
rodauth-oauth/
├── lib/
│ └── rodauth/
│ ├── oauth.rb # Main module entry point
│ ├── oauth/
│ │ ├── version.rb
│ │ ├── database_extensions.rb
│ │ ├── http_extensions.rb
│ │ ├── jwe_extensions.rb
│ │ └── ttl_store.rb
│ └── features/ # 34 feature files!
│ ├── oauth_base.rb # Foundation
│ ├── oauth_authorization_code_grant.rb
│ ├── oauth_pkce.rb
│ ├── oauth_jwt*.rb # JWT support (5 files)
│ ├── oidc.rb # OpenID Core
│ ├── oidc_*logout.rb # Logout flows (3 files)
│ ├── oauth_client_credentials_grant.rb
│ ├── oauth_device_code_grant.rb
│ ├── oauth_token_revocation.rb
│ ├── oauth_token_introspection.rb
│ ├── oauth_dynamic_client_registration.rb
│ ├── oauth_dpop.rb # DPoP support
│ ├── oauth_tls_client_auth.rb
│ ├── oauth_pushed_authorization_request.rb
│ ├── oauth_assertion_base.rb
│ └── ... (more features)
├── test/
│ ├── migrate/ # Database migrations
│ │ ├── 001_accounts.rb
│ │ ├── 003_oauth_applications.rb
│ │ ├── 004_oauth_grants.rb
│ │ ├── 005_pushed_requests.rb
│ │ ├── 006_saml_settings.rb
│ │ └── 007_dpop_proofs.rb
│ └── [multiple test directories with hundreds of tests]
├── examples/ # Full working examples
│ ├── authorization_server/
│ ├── oidc/
│ ├── jwt/
│ ├── device_grant/
│ ├── saml_assertion/
│ └── mtls/
├── templates/ # HTML/ERB templates
├── locales/ # i18n translations
├── doc/
└── [Gemfile, README, MIGRATION-GUIDE, etc.]
Feature Count: 34 Features!
The gem is completely modular. Each feature can be independently enabled:
Core OAuth Features:
oauth_base- Foundationoauth_authorization_code_grant- Authorization Code Flowoauth_implicit_grant- Implicit Flowoauth_client_credentials_grant- Client Credentials Flowoauth_device_code_grant- Device Code Flow
Token Management:
oauth_token_revocation- RFC 7009oauth_token_introspection- RFC 7662oauth_refresh_token- Refresh tokens
Security & Advanced:
oauth_pkce- RFC 7636 (what Clinch is using!)oauth_jwt- JWT Access Tokensoauth_jwt_bearer_grant- RFC 7523oauth_saml_bearer_grant- RFC 7522oauth_tls_client_auth- Mutual TLSoauth_dpop- Demonstrating Proof-of-Possessionoauth_jwt_secured_authorization_request- Request Objectsoauth_resource_indicators- RFC 8707oauth_pushed_authorization_request- RFC 9126
OpenID Connect:
oidc- Core OpenID Connectoidc_session_management- Session Managementoidc_rp_initiated_logout- RP-Initiated Logoutoidc_frontchannel_logout- Front-Channel Logoutoidc_backchannel_logout- Back-Channel Logoutoidc_dynamic_client_registration- Dynamic Registrationoidc_self_issued- Self-Issued Provider
Management & Discovery:
oauth_application_management- Client app dashboardoauth_grant_management- Grant management dashboardoauth_dynamic_client_registration- RFC 7591/7592oauth_jwt_jwks- JWKS endpoint
3. OIDC/OAuth Features Provided
Grant Types Supported (15 types!)
| Grant Type | Status | RFC/Spec |
|---|---|---|
| Authorization Code | Yes | RFC 6749 |
| Implicit | Optional | RFC 6749 |
| Client Credentials | Optional | RFC 6749 |
| Device Code | Optional | RFC 8628 |
| Refresh Token | Yes | RFC 6749 |
| JWT Bearer | Optional | RFC 7523 |
| SAML Bearer | Optional | RFC 7522 |
Response Types & Modes
Response Types:
code(Authorization Code) - Defaultid_token(OIDC Implicit) - Optionaltoken(Implicit) - Optionalid_token token(Hybrid) - Optionalcode id_token(Hybrid) - Optionalcode token(Hybrid) - Optionalcode id_token token(Hybrid) - Optional
Response Modes:
query(URL parameters)fragment(URL fragment)form_post(HTML form)jwt(JWT-based response)
OpenID Connect Features
✓ Certified for:
- Basic OP (OpenID Provider)
- Implicit OP
- Hybrid OP
- Config OP (Discovery)
- Dynamic OP (Dynamic Client Registration)
- Form Post OP
- 3rd Party-Init OP
- Session Management OP
- RP-Initiated Logout OP
- Front-Channel Logout OP
- Back-Channel Logout OP
✓ Standard Claims Support:
openid,email,profile,address,phonescopes- Automatic claim mapping per OpenID spec
- Custom claims via extension
✓ Token Features:
- JWT ID Tokens
- JWT Access Tokens
- Encrypted JWTs (JWE support)
- HMAC-SHA256 signing
- RSA/EC signing
- Custom token formats
Security Features
| Feature | Details |
|---|---|
| PKCE | RFC 7636 - Proof Key for Public Clients |
| Token Hashing | Bcrypt-based token storage (plain text optional) |
| DPoP | RFC 9449 - Demonstrating Proof-of-Possession |
| TLS Client Auth | RFC 8705 - Mutual TLS authentication |
| Request Objects | JWT-signed/encrypted authorization requests |
| Pushed Auth Requests | RFC 9126 - Pushed Authorization Requests |
| Token Introspection | RFC 7662 - Token validation without DB lookup |
| Token Revocation | RFC 7009 - Revoke tokens on demand |
Scopes & Authorization
- Configurable scope list per application
- Offline access support (refresh tokens)
- Scope-based access control
- Custom scope handlers
- Consent UI for user authorization
4. Architecture: How It Works
As a Plugin System
Rodauth-OAuth integrates with Roda as a plugin:
# This is how you configure it
class AuthServer < Roda
plugin :rodauth do
db database_connection
# Enable features
enable :login, :logout, :create_account, :oidc, :oidc_session_management,
:oauth_pkce, :oauth_authorization_code_grant
# Configure
oauth_application_scopes %w[openid email profile]
oauth_require_pkce true
hmac_secret "SECRET"
# Customize with blocks
oauth_jwt_keys("RS256" => [private_key])
oauth_jwt_public_keys("RS256" => [public_key])
end
end
Request Flow Architecture
1. Authorization Request
↓
rodauth validates params
↓
(if not auth'd) user logs in via rodauth
↓
(if first use) consent page rendered
↓
create oauth_grant (code, nonce, PKCE challenge, etc.)
↓
redirect with auth code
2. Token Exchange
↓
rodauth validates client (Basic/POST auth)
↓
validates code, redirect_uri, PKCE verifier
↓
creates access token (plain or JWT)
↓
creates refresh token
↓
returns JSON with tokens
3. UserInfo
↓
validate access token
↓
lookup grant/account
↓
return claims as JSON
Feature Composition
Features depend on each other. For example:
oidcdepends on:active_sessions,oauth_jwt,oauth_jwt_jwks,oauth_authorization_code_grant,oauth_implicit_grantoauth_pkcedepends on:oauth_authorization_code_grantoidc_rp_initiated_logoutdepends on:oidc
This is a strong dependency injection pattern.
5. Database Schema Requirements
Rodauth-OAuth Tables
accounts table (from rodauth)
CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
status_id INTEGER DEFAULT 1, -- unverified/verified/closed
email VARCHAR UNIQUE NOT NULL,
-- password-related columns (added by rodauth features)
password_hash VARCHAR,
-- other rodauth-managed columns
);
oauth_applications table (75+ columns!)
CREATE TABLE oauth_applications (
id INTEGER PRIMARY KEY,
account_id INTEGER FOREIGN KEY,
-- Basic info
name VARCHAR NOT NULL,
description VARCHAR,
homepage_url VARCHAR,
logo_uri VARCHAR,
tos_uri VARCHAR,
policy_uri VARCHAR,
-- OAuth credentials
client_id VARCHAR UNIQUE NOT NULL,
client_secret VARCHAR UNIQUE NOT NULL,
registration_access_token VARCHAR,
-- OAuth config
redirect_uri VARCHAR NOT NULL,
scopes VARCHAR NOT NULL,
token_endpoint_auth_method VARCHAR,
grant_types VARCHAR,
response_types VARCHAR,
response_modes VARCHAR,
-- JWT/JWKS
jwks_uri VARCHAR,
jwks TEXT,
jwt_public_key TEXT,
-- OIDC-specific
sector_identifier_uri VARCHAR,
application_type VARCHAR,
initiate_login_uri VARCHAR,
subject_type VARCHAR,
-- Token encryption algorithms
id_token_signed_response_alg VARCHAR,
id_token_encrypted_response_alg VARCHAR,
id_token_encrypted_response_enc VARCHAR,
userinfo_signed_response_alg VARCHAR,
userinfo_encrypted_response_alg VARCHAR,
userinfo_encrypted_response_enc VARCHAR,
-- Request object handling
request_object_signing_alg VARCHAR,
request_object_encryption_alg VARCHAR,
request_object_encryption_enc VARCHAR,
request_uris VARCHAR,
require_signed_request_object BOOLEAN,
-- PAR (Pushed Auth Requests)
require_pushed_authorization_requests BOOLEAN DEFAULT FALSE,
-- DPoP
dpop_bound_access_tokens BOOLEAN DEFAULT FALSE,
-- TLS Client Auth
tls_client_auth_subject_dn VARCHAR,
tls_client_auth_san_dns VARCHAR,
tls_client_auth_san_uri VARCHAR,
tls_client_auth_san_ip VARCHAR,
tls_client_auth_san_email VARCHAR,
tls_client_certificate_bound_access_tokens BOOLEAN DEFAULT FALSE,
-- Logout URIs
post_logout_redirect_uris VARCHAR,
frontchannel_logout_uri VARCHAR,
frontchannel_logout_session_required BOOLEAN DEFAULT FALSE,
backchannel_logout_uri VARCHAR,
backchannel_logout_session_required BOOLEAN DEFAULT FALSE,
-- Response encryption
authorization_signed_response_alg VARCHAR,
authorization_encrypted_response_alg VARCHAR,
authorization_encrypted_response_enc VARCHAR,
contact_info VARCHAR,
software_id VARCHAR,
software_version VARCHAR
);
oauth_grants table (everything in one table!)
CREATE TABLE oauth_grants (
id INTEGER PRIMARY KEY,
account_id INTEGER FOREIGN KEY, -- nullable for client credentials
oauth_application_id INTEGER FOREIGN KEY,
sub_account_id INTEGER, -- for context-based ownership
type VARCHAR, -- 'authorization_code', 'refresh_token', etc.
-- Authorization code flow
code VARCHAR UNIQUE (per app),
redirect_uri VARCHAR,
-- Tokens (stored hashed or plain)
token VARCHAR UNIQUE,
token_hash VARCHAR UNIQUE,
refresh_token VARCHAR UNIQUE,
refresh_token_hash VARCHAR UNIQUE,
-- Expiry
expires_in TIMESTAMP NOT NULL,
revoked_at TIMESTAMP,
-- Scopes
scopes VARCHAR NOT NULL,
access_type VARCHAR DEFAULT 'offline', -- 'offline' or 'online'
-- PKCE
code_challenge VARCHAR,
code_challenge_method VARCHAR, -- 'plain' or 'S256'
-- Device Code Grant
user_code VARCHAR UNIQUE,
last_polled_at TIMESTAMP,
-- TLS Client Auth
certificate_thumbprint VARCHAR,
-- Resource Indicators
resource VARCHAR,
-- OpenID Connect
nonce VARCHAR,
acr VARCHAR, -- Authentication Context Class
claims_locales VARCHAR,
claims VARCHAR, -- custom OIDC claims
-- DPoP
dpop_jkt VARCHAR -- DPoP key thumbprint
);
Optional Tables for Advanced Features
-- For Pushed Authorization Requests
CREATE TABLE oauth_pushed_requests (
request_uri VARCHAR UNIQUE PRIMARY KEY,
oauth_application_id INTEGER FOREIGN KEY,
params TEXT, -- JSON params
created_at TIMESTAMP
);
-- For SAML Assertion Grant
CREATE TABLE oauth_saml_settings (
id INTEGER PRIMARY KEY,
oauth_application_id INTEGER FOREIGN KEY,
idp_url VARCHAR,
certificate TEXT,
-- ...
);
-- For DPoP
CREATE TABLE oauth_dpop_proofs (
id INTEGER PRIMARY KEY,
oauth_grant_id INTEGER FOREIGN KEY,
jti VARCHAR UNIQUE,
created_at TIMESTAMP
);
Key Differences from Your Implementation
| Aspect | Your Implementation | Rodauth-OAuth |
|---|---|---|
| Authorization Codes | Separate table | In oauth_grants |
| Access Tokens | Separate table | In oauth_grants |
| Refresh Tokens | Not implemented | In oauth_grants |
| Token Hashing | Not done | Bcrypt (default) |
| Applications | Basic (name, client_id, secret) | 75+ columns for full spec |
| PKCE | Simple columns | Built-in feature |
| Account Data | In users table | In accounts table |
| Session Management | Session model | Rodauth's account_active_session_keys |
| User Consent | OidcUserConsent table | In memory or via hooks |
6. Integration Points with Rails
Via Rodauth-Rails Wrapper
Rodauth-OAuth can be used in Rails through the rodauth-rails gem:
# Install generator
gem 'rodauth-rails'
bundle install
rails generate rodauth:install
rails generate rodauth:oauth:install # Generates OIDC tables/migrations
rails generate rodauth:oauth:views # Generates templates
Generated Components
-
Migration:
db/migrate/*_create_rodauth_oauth.rb- Creates all OAuth tables
- Customizable column names via config
-
Models:
app/models/RodauthApp(configuration)OauthApplication(client app)OauthGrant(grants/tokens)- Customizable!
-
Views:
app/views/rodauth/- Authorization consent form
- Application management dashboard
- Grant management dashboard
-
Lib:
lib/rodauth_app.rb- Main rodauth configuration
Rails Controller Integration
class BooksController < ApplicationController
before_action :require_oauth_authorization, only: %i[create update]
before_action :require_oauth_authorization_scopes, only: %i[create update]
private
def require_oauth_authorization(scope = "books.read")
rodauth.require_oauth_authorization(scope)
end
end
Or for route protection:
# config/routes.rb
namespace :api do
resources :books, only: [:index] # protected by rodauth
end
7. Architectural Comparison
Your Custom Implementation
Pros:
- Simple, easy to understand
- Minimal dependencies (just JWT, OpenSSL)
- Lightweight database (small tables)
- Direct Rails integration
- Minimal features = less surface area
Cons:
- Only supports Authorization Code + PKCE
- No refresh tokens
- No token revocation/introspection
- No client credentials grant
- No JWT access tokens
- Manual consent management
- Not standards-compliant (missing many OIDC features)
- Will need continuous custom development
Architecture:
Rails Controller
↓
OidcController (450 lines)
↓
OidcAuthorizationCode Model
OidcAccessToken Model
OidcUserConsent Model
↓
Database
Rodauth-OAuth Implementation
Pros:
- 34 built-in features
- OpenID-Certified
- Production-tested
- Highly configurable
- Comprehensive token management
- Standards-compliant (RFCs & OpenID specs)
- Strong test coverage (hundreds of tests)
- Active maintenance
Cons:
- More complex (needs Roda/Rodauth knowledge)
- Larger codebase to learn
- Rails integration via wrapper (extra layer)
- Different paradigm (Roda vs Rails)
- More database columns to manage
Architecture:
Roda App
↓
Rodauth Plugin (configurable)
├── oauth_base (foundation)
├── oauth_authorization_code_grant
├── oauth_pkce
├── oauth_jwt
├── oidc (all OpenID features)
├── [other optional features]
↓
Sequel ORM
↓
Database (flexible schema)
8. Feature Comparison Matrix
| Feature | Your Impl | Rodauth-OAuth | Notes |
|---|---|---|---|
| Authorization Code | ✓ | ✓ | Both support |
| PKCE | ✓ | ✓ | Both support |
| Refresh Tokens | ✗ | ✓ | You'd need to add |
| Implicit Flow | ✗ | ✓ Optional | Legacy, not recommended |
| Client Credentials | ✗ | ✓ Optional | Machine-to-machine |
| Device Code | ✗ | ✓ Optional | IoT devices |
| JWT Bearer Grant | ✗ | ✓ Optional | Service accounts |
| SAML Bearer Grant | ✗ | ✓ Optional | Enterprise SAML |
| JWT Access Tokens | ✗ | ✓ Optional | Stateless tokens |
| Token Revocation | ✗ | ✓ | RFC 7009 |
| Token Introspection | ✗ | ✓ | RFC 7662 |
| Pushed Auth Requests | ✗ | ✓ Optional | RFC 9126 |
| DPoP | ✗ | ✓ Optional | RFC 9449 |
| TLS Client Auth | ✗ | ✓ Optional | RFC 8705 |
| OpenID Connect | ✓ Basic | ✓ Full | Yours is minimal |
| ID Tokens | ✓ | ✓ | Both support |
| UserInfo Endpoint | ✓ | ✓ | Both support |
| Discovery | ✓ | ✓ | Both support |
| Session Management | ✗ | ✓ Optional | Check session iframe |
| RP-Init Logout | ✓ | ✓ | Both support |
| Front-Channel Logout | ✗ | ✓ | Iframe-based |
| Back-Channel Logout | ✗ | ✓ | Server-to-server |
| Dynamic Client Reg | ✗ | ✓ Optional | RFC 7591/7592 |
| Token Hashing | ✗ | ✓ | Security best practice |
| Scopes | ✓ | ✓ | Both support |
| Custom Claims | ✓ Manual | ✓ Built-in | Yours via JWT service |
| Consent UI | ✓ | ✓ | Both support |
| Client App Dashboard | ✗ | ✓ Optional | Built-in |
| Grant Management Dashboard | ✗ | ✓ Optional | Built-in |
9. Integration Complexity Analysis
Switching to Rodauth-OAuth
Medium Complexity (Not Trivial, but Doable)
What you'd need to do:
-
Learn Roda + Rodauth
- Move from pure Rails to Roda-based architecture
- Understand rodauth feature system
- Time: 1-2 weeks for Rails developers
-
Migrate Database Schema
- Consolidate tables: authorization codes + access tokens → oauth_grants
- Rename columns to match rodauth conventions
- Add many new columns for feature support
- Migration script needed: ~100-300 lines
- Time: 1 week development + testing
-
Replace Your OIDC Code
- Replace your 450-line OidcController
- Remove your 3 model files
- Keep your OidcJwtService (mostly compatible)
- Add rodauth configuration
- Time: 1-2 weeks
-
Update Application/Client Model
- Expand
Applicationmodel properties - Support all OAuth scopes, grant types, response types
- Time: 3-5 days
- Expand
-
Create Migrations from Template
- Use rodauth-oauth migration templates
- Customize for your database
- Time: 2-3 days
-
Testing
- Write integration tests
- Verify all OAuth flows still work
- Check token validation logic
- Time: 2-3 weeks
Total Effort: 4-8 weeks for experienced team
Keeping Your Implementation (Custom Path)
What You'd Need to Add
To reach feature parity with rodauth-oauth (for common use cases):
-
Refresh Token Support (1-2 weeks)
- Database schema
- Token refresh endpoint
- Token validation logic
-
Token Revocation (1 week)
- Revocation endpoint
- Token blacklist/invalidation
-
Token Introspection (1 week)
- Introspection endpoint
- Token validation without DB lookup
-
Client Credentials Grant (2 weeks)
- Endpoint logic
- Client authentication
- Token generation for apps
-
Improved Security (ongoing)
- Token hashing (bcrypt)
- Rate limiting
- Additional validation
-
Advanced OIDC Features
- Session Management
- Logout endpoints (front/back-channel)
- Dynamic client registration
- Device code flow
Total Effort: 2-3 months ongoing
10. Key Findings & Recommendations
What Rodauth-OAuth Does Better
-
Standards Compliance
- Certified for 11 OpenID Connect profiles
- Implements 20+ RFCs and specs
- Regular spec updates
-
Security
- Token hashing by default
- DPoP support (token binding)
- TLS client auth
- Proper scope enforcement
-
Features
- 34 optional features (you get what you need)
- No bloat - only enable what you use
- Mature refresh token handling
-
Production Readiness
- Thousands of test cases
- Open source (auditable)
- Active maintenance
- Real-world deployments
-
Flexibility
- Works with any SQL database
- Highly configurable column names
- Custom behavior via overrides
- Multiple app types support
What Your Implementation Does Better
-
Simplicity
- Fewer dependencies
- Smaller codebase
- Easier to reason about
-
Rails Integration
- Direct Rails ActiveRecord
- No Roda learning curve
- Familiar patterns
-
Control
- Full control of every line
- No surprises
- Easy to debug
Recommendation
Use Rodauth-OAuth IF:
- You need a production OIDC/OAuth provider
- You want standards compliance
- You plan to support multiple grant types
- You need token revocation/introspection
- You want a maintained codebase
Keep Your Custom Implementation IF:
- Authorization Code + PKCE only is sufficient
- You're avoiding Roda/Rodauth learning curve
- Your org standardizes on Rails patterns
- You have time to add features incrementally
- You need maximum control and simplicity
Hybrid Approach:
- Use rodauth-oauth for OIDC/OAuth server components
- Keep your Rails app for other features
- They can coexist (separate services)
11. Migration Path (If You Decide to Switch)
Phase 1: Preparation (Week 1-2)
- Set up separate Roda app with rodauth-oauth
- Run alongside your existing service
- Parallel user testing
Phase 2: Data Migration (Week 2-3)
- Create migration script for oauth_grants table
- Backfill existing auth codes and tokens
- Verify data integrity
Phase 3: Gradual Cutover (Week 4-6)
- Direct some OAuth clients to new server
- Monitor for issues
- Swap over when confident
Phase 4: Cleanup (Week 6+)
- Remove custom OIDC code
- Decommission old tables
- Document new architecture
12. Code Examples
Rodauth-OAuth: Minimal Setup
# Gemfile
gem 'roda'
gem 'rodauth-oauth'
gem 'sequel'
# lib/auth_server.rb
class AuthServer < Roda
plugin :render, views: 'views'
plugin :sessions, secret: 'SECRET'
plugin :rodauth do
db DB
enable :login, :logout, :create_account, :oidc, :oauth_pkce,
:oauth_authorization_code_grant, :oauth_token_introspection
oauth_application_scopes %w[openid email profile]
oauth_require_pkce true
hmac_secret 'HMAC_SECRET'
oauth_jwt_keys('RS256' => [private_key])
end
route do |r|
r.rodauth # All OAuth routes automatically mounted
# Your custom routes
r.get 'api' do
rodauth.require_oauth_authorization('api.read')
# return data
end
end
end
Your Current Approach: Manual
# app/controllers/oidc_controller.rb
def authorize
validate_params
find_application
check_authentication
handle_consent
generate_code
redirect_with_code
end
def token
extract_client_credentials
find_application
validate_code
check_pkce
generate_tokens
return_json
end
Summary Table
| Aspect | Your Implementation | Rodauth-OAuth |
|---|---|---|
| Framework | Rails | Roda |
| Database ORM | ActiveRecord | Sequel |
| Grant Types | 1 (Auth Code) | 7+ options |
| Token Types | Opaque | Opaque or JWT |
| Security Features | Basic | Advanced (DPoP, MTLS, etc.) |
| OIDC Compliance | Partial | Full (Certified) |
| Lines of Code | ~1000 | ~10,000+ |
| Features | 2-3 | 34 optional |
| Maintenance Burden | High | Low (OSS) |
| Learning Curve | Low | Medium (Roda) |
| Production Ready | Yes | Yes |
| Community | Just you | Active |