Skip to content

Commit b2030df

Browse files
committed
Return only scopes requested ( OpenID conformance test. Update README
1 parent 07cddf5 commit b2030df

File tree

3 files changed

+47
-22
lines changed

3 files changed

+47
-22
lines changed

README.md

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -347,27 +347,39 @@ services:
347347
348348
Create a `.env` file in the same directory:
349349

350+
**Generate required secrets first:**
351+
352+
```bash
353+
# Generate SECRET_KEY_BASE (required)
354+
openssl rand -hex 64
355+
356+
# Generate OIDC private key (optional - auto-generated if not provided)
357+
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
358+
cat private_key.pem # Copy the output into OIDC_PRIVATE_KEY below
359+
```
360+
361+
**Then create `.env`:**
362+
350363
```bash
351-
# Generate with: openssl rand -hex 64
352-
SECRET_KEY_BASE=your-secret-key-here
364+
# Rails Secret (REQUIRED)
365+
SECRET_KEY_BASE=paste-output-from-openssl-rand-hex-64-here
353366
354-
# Application URLs
367+
# Application URLs (REQUIRED)
355368
CLINCH_HOST=https://auth.yourdomain.com
356369
CLINCH_FROM_EMAIL=noreply@yourdomain.com
357370
358-
# SMTP Settings
371+
# SMTP Settings (REQUIRED for invitations and password resets)
359372
SMTP_ADDRESS=smtp.example.com
360373
SMTP_PORT=587
361374
SMTP_DOMAIN=yourdomain.com
362375
SMTP_USERNAME=your-smtp-username
363376
SMTP_PASSWORD=your-smtp-password
364377
365-
# OIDC (optional - generates temporary key if not set)
366-
# Generate with: openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
367-
# Then: OIDC_PRIVATE_KEY=$(cat private_key.pem)
378+
# OIDC Private Key (OPTIONAL - generates temporary key if not provided)
379+
# For production, generate a persistent key and paste the ENTIRE contents here
368380
OIDC_PRIVATE_KEY=
369381
370-
# Optional: Force SSL redirects (if not behind a reverse proxy handling SSL)
382+
# Optional: Force SSL redirects (only if NOT behind a reverse proxy handling SSL)
371383
FORCE_SSL=false
372384
```
373385

app/services/oidc_jwt_service.rb

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,38 @@ class OidcJwtService
33

44
class << self
55
# Generate an ID token (JWT) for the user
6-
def generate_id_token(user, application, consent: nil, nonce: nil, access_token: nil, auth_time: nil, acr: nil)
6+
def generate_id_token(user, application, consent: nil, nonce: nil, access_token: nil, auth_time: nil, acr: nil, scopes: "openid")
77
now = Time.current.to_i
88
# Use application's configured ID token TTL (defaults to 1 hour)
99
ttl = application.id_token_expiry_seconds
1010

1111
# Use pairwise SID from consent if available, fallback to user ID
1212
subject = consent&.sid || user.id.to_s
1313

14+
# Parse scopes (space-separated string)
15+
requested_scopes = scopes.to_s.split
16+
17+
# Required claims (always included per OIDC Core spec)
1418
payload = {
1519
iss: issuer_url,
1620
sub: subject,
1721
aud: application.client_id,
1822
exp: now + ttl,
19-
iat: now,
20-
email: user.email_address,
21-
email_verified: true,
22-
preferred_username: user.username.presence || user.email_address,
23-
name: user.name.presence || user.email_address
23+
iat: now
2424
}
2525

26+
# Email claims (only if 'email' scope requested)
27+
if requested_scopes.include?("email")
28+
payload[:email] = user.email_address
29+
payload[:email_verified] = true
30+
end
31+
32+
# Profile claims (only if 'profile' scope requested)
33+
if requested_scopes.include?("profile")
34+
payload[:preferred_username] = user.username.presence || user.email_address
35+
payload[:name] = user.name.presence || user.email_address
36+
end
37+
2638
# Add nonce if provided (OIDC requires this for implicit flow)
2739
payload[:nonce] = nonce if nonce.present?
2840

@@ -44,12 +56,13 @@ def generate_id_token(user, application, consent: nil, nonce: nil, access_token:
4456
payload[:at_hash] = at_hash
4557
end
4658

47-
# Add groups if user has any
48-
if user.groups.any?
59+
# Groups claims (only if 'groups' scope requested)
60+
if requested_scopes.include?("groups") && user.groups.any?
4961
payload[:groups] = user.groups.pluck(:name)
5062
end
5163

5264
# Merge custom claims from groups (arrays are combined, not overwritten)
65+
# Note: Custom claims from groups are always merged (not scope-dependent)
5366
user.groups.each do |group|
5467
payload = deep_merge_claims(payload, group.parsed_custom_claims)
5568
end

docs/beta-checklist.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -204,13 +204,13 @@ This checklist ensures Clinch meets security, quality, and documentation standar
204204
- [ ] Document backup code security (single-use, store securely)
205205
- [ ] Document admin password security requirements
206206

207-
### Future Security Enhancements
208-
- [ ] Rate limiting on authentication endpoints
209-
- [ ] Account lockout after N failed attempts
207+
### Future Security Enhancements (Post-Beta)
208+
- [x] Rate limiting on authentication endpoints (comprehensive coverage implemented)
209+
- [ ] Account lockout after N failed attempts (rate limiting provides similar protection)
210210
- [ ] Admin audit logging
211-
- [ ] Security event notifications
212-
- [ ] Brute force detection
213-
- [ ] Suspicious login detection
211+
- [ ] Security event notifications (email/webhook alerts for suspicious activity)
212+
- [ ] Advanced brute force detection (pattern analysis beyond rate limiting)
213+
- [ ] Suspicious login detection (geolocation, device fingerprinting)
214214
- [ ] IP allowlist/blocklist
215215

216216
## External Security Review

0 commit comments

Comments
 (0)