Return only scopes requested ( OpenID conformance test. Update README
This commit is contained in:
30
README.md
30
README.md
@@ -347,27 +347,39 @@ services:
|
|||||||
|
|
||||||
Create a `.env` file in the same directory:
|
Create a `.env` file in the same directory:
|
||||||
|
|
||||||
```bash
|
**Generate required secrets first:**
|
||||||
# Generate with: openssl rand -hex 64
|
|
||||||
SECRET_KEY_BASE=your-secret-key-here
|
|
||||||
|
|
||||||
# Application URLs
|
```bash
|
||||||
|
# Generate SECRET_KEY_BASE (required)
|
||||||
|
openssl rand -hex 64
|
||||||
|
|
||||||
|
# Generate OIDC private key (optional - auto-generated if not provided)
|
||||||
|
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
|
||||||
|
cat private_key.pem # Copy the output into OIDC_PRIVATE_KEY below
|
||||||
|
```
|
||||||
|
|
||||||
|
**Then create `.env`:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Rails Secret (REQUIRED)
|
||||||
|
SECRET_KEY_BASE=paste-output-from-openssl-rand-hex-64-here
|
||||||
|
|
||||||
|
# Application URLs (REQUIRED)
|
||||||
CLINCH_HOST=https://auth.yourdomain.com
|
CLINCH_HOST=https://auth.yourdomain.com
|
||||||
CLINCH_FROM_EMAIL=noreply@yourdomain.com
|
CLINCH_FROM_EMAIL=noreply@yourdomain.com
|
||||||
|
|
||||||
# SMTP Settings
|
# SMTP Settings (REQUIRED for invitations and password resets)
|
||||||
SMTP_ADDRESS=smtp.example.com
|
SMTP_ADDRESS=smtp.example.com
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_DOMAIN=yourdomain.com
|
SMTP_DOMAIN=yourdomain.com
|
||||||
SMTP_USERNAME=your-smtp-username
|
SMTP_USERNAME=your-smtp-username
|
||||||
SMTP_PASSWORD=your-smtp-password
|
SMTP_PASSWORD=your-smtp-password
|
||||||
|
|
||||||
# OIDC (optional - generates temporary key if not set)
|
# OIDC Private Key (OPTIONAL - generates temporary key if not provided)
|
||||||
# Generate with: openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
|
# For production, generate a persistent key and paste the ENTIRE contents here
|
||||||
# Then: OIDC_PRIVATE_KEY=$(cat private_key.pem)
|
|
||||||
OIDC_PRIVATE_KEY=
|
OIDC_PRIVATE_KEY=
|
||||||
|
|
||||||
# Optional: Force SSL redirects (if not behind a reverse proxy handling SSL)
|
# Optional: Force SSL redirects (only if NOT behind a reverse proxy handling SSL)
|
||||||
FORCE_SSL=false
|
FORCE_SSL=false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ class OidcJwtService
|
|||||||
|
|
||||||
class << self
|
class << self
|
||||||
# Generate an ID token (JWT) for the user
|
# Generate an ID token (JWT) for the user
|
||||||
def generate_id_token(user, application, consent: nil, nonce: nil, access_token: nil, auth_time: nil, acr: nil)
|
def generate_id_token(user, application, consent: nil, nonce: nil, access_token: nil, auth_time: nil, acr: nil, scopes: "openid")
|
||||||
now = Time.current.to_i
|
now = Time.current.to_i
|
||||||
# Use application's configured ID token TTL (defaults to 1 hour)
|
# Use application's configured ID token TTL (defaults to 1 hour)
|
||||||
ttl = application.id_token_expiry_seconds
|
ttl = application.id_token_expiry_seconds
|
||||||
@@ -11,18 +11,30 @@ class OidcJwtService
|
|||||||
# Use pairwise SID from consent if available, fallback to user ID
|
# Use pairwise SID from consent if available, fallback to user ID
|
||||||
subject = consent&.sid || user.id.to_s
|
subject = consent&.sid || user.id.to_s
|
||||||
|
|
||||||
|
# Parse scopes (space-separated string)
|
||||||
|
requested_scopes = scopes.to_s.split
|
||||||
|
|
||||||
|
# Required claims (always included per OIDC Core spec)
|
||||||
payload = {
|
payload = {
|
||||||
iss: issuer_url,
|
iss: issuer_url,
|
||||||
sub: subject,
|
sub: subject,
|
||||||
aud: application.client_id,
|
aud: application.client_id,
|
||||||
exp: now + ttl,
|
exp: now + ttl,
|
||||||
iat: now,
|
iat: now
|
||||||
email: user.email_address,
|
|
||||||
email_verified: true,
|
|
||||||
preferred_username: user.username.presence || user.email_address,
|
|
||||||
name: user.name.presence || user.email_address
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Email claims (only if 'email' scope requested)
|
||||||
|
if requested_scopes.include?("email")
|
||||||
|
payload[:email] = user.email_address
|
||||||
|
payload[:email_verified] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Profile claims (only if 'profile' scope requested)
|
||||||
|
if requested_scopes.include?("profile")
|
||||||
|
payload[:preferred_username] = user.username.presence || user.email_address
|
||||||
|
payload[:name] = user.name.presence || user.email_address
|
||||||
|
end
|
||||||
|
|
||||||
# Add nonce if provided (OIDC requires this for implicit flow)
|
# Add nonce if provided (OIDC requires this for implicit flow)
|
||||||
payload[:nonce] = nonce if nonce.present?
|
payload[:nonce] = nonce if nonce.present?
|
||||||
|
|
||||||
@@ -44,12 +56,13 @@ class OidcJwtService
|
|||||||
payload[:at_hash] = at_hash
|
payload[:at_hash] = at_hash
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add groups if user has any
|
# Groups claims (only if 'groups' scope requested)
|
||||||
if user.groups.any?
|
if requested_scopes.include?("groups") && user.groups.any?
|
||||||
payload[:groups] = user.groups.pluck(:name)
|
payload[:groups] = user.groups.pluck(:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Merge custom claims from groups (arrays are combined, not overwritten)
|
# Merge custom claims from groups (arrays are combined, not overwritten)
|
||||||
|
# Note: Custom claims from groups are always merged (not scope-dependent)
|
||||||
user.groups.each do |group|
|
user.groups.each do |group|
|
||||||
payload = deep_merge_claims(payload, group.parsed_custom_claims)
|
payload = deep_merge_claims(payload, group.parsed_custom_claims)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -204,13 +204,13 @@ This checklist ensures Clinch meets security, quality, and documentation standar
|
|||||||
- [ ] Document backup code security (single-use, store securely)
|
- [ ] Document backup code security (single-use, store securely)
|
||||||
- [ ] Document admin password security requirements
|
- [ ] Document admin password security requirements
|
||||||
|
|
||||||
### Future Security Enhancements
|
### Future Security Enhancements (Post-Beta)
|
||||||
- [ ] Rate limiting on authentication endpoints
|
- [x] Rate limiting on authentication endpoints (comprehensive coverage implemented)
|
||||||
- [ ] Account lockout after N failed attempts
|
- [ ] Account lockout after N failed attempts (rate limiting provides similar protection)
|
||||||
- [ ] Admin audit logging
|
- [ ] Admin audit logging
|
||||||
- [ ] Security event notifications
|
- [ ] Security event notifications (email/webhook alerts for suspicious activity)
|
||||||
- [ ] Brute force detection
|
- [ ] Advanced brute force detection (pattern analysis beyond rate limiting)
|
||||||
- [ ] Suspicious login detection
|
- [ ] Suspicious login detection (geolocation, device fingerprinting)
|
||||||
- [ ] IP allowlist/blocklist
|
- [ ] IP allowlist/blocklist
|
||||||
|
|
||||||
## External Security Review
|
## External Security Review
|
||||||
|
|||||||
Reference in New Issue
Block a user