clavis 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.brakeman.ignore +1 -0
- data/CHANGELOG.md +27 -15
- data/README.md +271 -537
- data/Rakefile +52 -4
- data/lib/clavis/version.rb +1 -1
- data/lib/generators/clavis/install_generator.rb +34 -17
- data/lib/generators/clavis/user_method/user_method_generator.rb +5 -16
- data/llms.md +256 -347
- metadata +2 -4
- data/UPGRADE.md +0 -57
- data/docs/integration.md +0 -272
- data/testing_plan.md +0 -710
data/llms.md
CHANGED
@@ -1,300 +1,199 @@
|
|
1
|
-
# Clavis:
|
1
|
+
# Clavis: API Documentation for LLM Integration
|
2
2
|
|
3
|
-
This document provides technical information about the Clavis gem for large language models.
|
3
|
+
This document provides structured technical information about the Clavis gem for large language models and AI assistants. It's designed to be a comprehensive reference for automated code generation and assistance with Clavis OAuth integration.
|
4
4
|
|
5
|
-
##
|
5
|
+
## Reference Index
|
6
6
|
|
7
|
-
|
7
|
+
- [Quick Start](#quick-start-reference) - Minimal implementation steps
|
8
|
+
- [Installation](#installation-details) - What the generator creates
|
9
|
+
- [Configuration](#required-configuration) - Setting up providers
|
10
|
+
- [User Creation](#critical-step-customize-user-creation) - Required customization
|
11
|
+
- [Auth Routes](#core-routes) - Automatic route setup
|
12
|
+
- [Auth Hash](#auth-hash-structure) - OAuth data structure
|
13
|
+
- [Controllers](#controller-example) - Controller integration
|
14
|
+
- [Views](#view-integration) - Button rendering
|
15
|
+
- [Password Integration](#password-integration) - has_secure_password handling
|
16
|
+
- [Providers](#available-providers) - Supported OAuth providers
|
17
|
+
- [Custom Providers](#custom-provider-example) - Creating custom providers
|
18
|
+
- [Error Handling](#common-errors-and-solutions) - Troubleshooting common issues
|
19
|
+
- [Security](#security-features) - Security implementation details
|
20
|
+
- [Environment Variables](#environment-variables-summary) - Required environment variables
|
21
|
+
|
22
|
+
## Quick Start Reference
|
8
23
|
|
9
24
|
```ruby
|
10
25
|
# Step 1: Add to Gemfile
|
11
26
|
gem 'clavis'
|
12
|
-
```
|
13
27
|
|
14
|
-
|
15
|
-
# Step 2: Run the installation generator
|
28
|
+
# Step 2: Run installation
|
16
29
|
rails generate clavis:install
|
17
30
|
rails db:migrate
|
18
|
-
```
|
19
31
|
|
20
|
-
|
21
|
-
# Step 3: Configure a provider
|
32
|
+
# Step 3: Configure provider
|
22
33
|
Clavis.configure do |config|
|
23
34
|
config.providers = {
|
24
35
|
github: {
|
25
36
|
client_id: ENV["GITHUB_CLIENT_ID"],
|
26
|
-
client_secret: ENV["GITHUB_CLIENT_SECRET"]
|
37
|
+
client_secret: ENV["GITHUB_CLIENT_SECRET"],
|
38
|
+
redirect_uri: "https://your-app.com/auth/github/callback"
|
27
39
|
}
|
28
40
|
}
|
29
|
-
|
30
|
-
# Optional: Customize the path (default is '/auth/:provider/callback')
|
31
|
-
# config.default_callback_path = '/oauth/:provider/callback'
|
32
41
|
end
|
42
|
+
|
43
|
+
# Step 4: Add button to view
|
44
|
+
<%= clavis_oauth_button :github %>
|
45
|
+
|
46
|
+
# Step 5: CRITICAL - Customize user creation
|
47
|
+
# Edit app/models/concerns/clavis_user_methods.rb
|
33
48
|
```
|
34
49
|
|
35
|
-
|
50
|
+
## Installation Details
|
36
51
|
|
52
|
+
The generator automatically:
|
37
53
|
1. Creates migrations for OAuth identities
|
38
54
|
2. Mounts the engine at `/auth`
|
39
55
|
3. Creates configuration initializer
|
40
56
|
4. Adds `Clavis::Models::OauthAuthenticatable` to User model
|
57
|
+
5. Creates a ClavisUserMethods concern for user creation
|
41
58
|
|
42
|
-
|
59
|
+
### Required Configuration
|
43
60
|
|
44
|
-
```
|
45
|
-
|
61
|
+
```ruby
|
62
|
+
# config/initializers/clavis.rb
|
63
|
+
Clavis.configure do |config|
|
64
|
+
config.providers = {
|
65
|
+
google: {
|
66
|
+
client_id: ENV["GOOGLE_CLIENT_ID"],
|
67
|
+
client_secret: ENV["GOOGLE_CLIENT_SECRET"],
|
68
|
+
redirect_uri: "https://your-app.com/auth/google/callback"
|
69
|
+
}
|
70
|
+
}
|
71
|
+
end
|
46
72
|
```
|
47
73
|
|
48
|
-
###
|
49
|
-
|
50
|
-
1. Use the standard ERB syntax with `<%= %>` for OAuth buttons - the helper returns html_safe content
|
51
|
-
2. The gem automatically handles route setup when mounted at `/auth` - no additional route configuration needed
|
52
|
-
3. Always use the complete callback URI in provider configuration (e.g., `https://your-app.com/auth/github/callback`)
|
53
|
-
4. If you customize the mount path, make sure to update the `default_callback_path` configuration accordingly
|
54
|
-
|
55
|
-
## Table of Contents
|
56
|
-
|
57
|
-
1. [Overview](#overview)
|
58
|
-
2. [Architecture](#architecture)
|
59
|
-
3. [Core Components](#core-components)
|
60
|
-
4. [Implementation Details](#implementation-details)
|
61
|
-
5. [Usage Examples](#usage-examples)
|
62
|
-
6. [Customization Guide](#customization-guide)
|
63
|
-
7. [Error Handling](#error-handling)
|
64
|
-
8. [Security Considerations](#security-considerations)
|
65
|
-
9. [API Reference](#api-reference)
|
66
|
-
10. [Rate Limiting](#rate-limiting)
|
67
|
-
|
68
|
-
## Overview
|
69
|
-
|
70
|
-
Clavis implements OAuth 2.0 and OpenID Connect (OIDC) for Rails. It focuses on "Sign in with ____" functionality while maintaining security standards.
|
71
|
-
|
72
|
-
### Key Assumptions
|
74
|
+
### Auth Callback URI Configuration
|
73
75
|
|
74
|
-
|
75
|
-
2. Existing User model and authentication
|
76
|
-
3. Speed over detailed configuration
|
77
|
-
|
78
|
-
## Architecture
|
79
|
-
|
80
|
-
### Core Components
|
81
|
-
|
82
|
-
1. **Configuration System** - Stores provider settings and validates configuration
|
83
|
-
2. **Provider Framework** - Implements OAuth/OIDC flows with provider-specific logic
|
84
|
-
3. **Authentication Flow** - Handles requests and callbacks with CSRF protection
|
85
|
-
4. **User Management** - Maps OAuth responses to user records
|
86
|
-
5. **View Components** - Button helpers with provider-specific styling
|
87
|
-
6. **Rails Integration** - Routes, generators, and existing auth integration
|
88
|
-
|
89
|
-
## Implementation Details
|
90
|
-
|
91
|
-
### Callback URI Format
|
92
|
-
|
93
|
-
Always use the complete callback URI:
|
76
|
+
Always use the complete callback URI in both Clavis config and provider developer console:
|
94
77
|
|
95
78
|
```
|
96
79
|
https://your-domain.com/auth/:provider/callback
|
97
80
|
```
|
98
81
|
|
99
|
-
Common
|
82
|
+
Common error: `redirect_uri_mismatch` - caused by URI mismatch between your code and provider console settings.
|
100
83
|
|
101
|
-
###
|
102
|
-
|
103
|
-
The Clavis engine is mounted at `/auth` by default, which creates these routes:
|
84
|
+
### Core Routes
|
104
85
|
|
105
86
|
```
|
106
|
-
/auth
|
107
|
-
/auth/
|
108
|
-
/auth/:provider - Generic provider route
|
109
|
-
/auth/:provider/callback - Generic callback route
|
87
|
+
/auth/:provider - Initiates OAuth flow
|
88
|
+
/auth/:provider/callback - Handles OAuth callback
|
110
89
|
```
|
111
90
|
|
112
|
-
These routes are automatically registered
|
91
|
+
These routes are automatically registered via:
|
113
92
|
|
114
93
|
```ruby
|
115
94
|
# config/routes.rb (added by generator)
|
116
95
|
mount Clavis::Engine => "/auth"
|
117
96
|
```
|
118
97
|
|
119
|
-
|
120
|
-
|
121
|
-
You can customize the path in two ways:
|
98
|
+
## Critical Step: Customize User Creation
|
122
99
|
|
123
|
-
|
124
|
-
```ruby
|
125
|
-
# config/routes.rb
|
126
|
-
mount Clavis::Engine => "/oauth"
|
127
|
-
```
|
100
|
+
You MUST customize the user creation code to include all required fields for your User model:
|
128
101
|
|
129
|
-
2. **Update the callback path configuration**:
|
130
102
|
```ruby
|
131
|
-
#
|
132
|
-
|
133
|
-
#
|
134
|
-
config.default_callback_path = "/oauth/:provider/callback"
|
135
|
-
end
|
136
|
-
```
|
137
|
-
|
138
|
-
When customizing paths, make sure that:
|
139
|
-
1. The provider configuration's redirect URIs match your custom paths
|
140
|
-
2. Both the engine mount point and the `default_callback_path` are updated consistently
|
141
|
-
|
142
|
-
### OAuth Flow Implementation
|
143
|
-
|
144
|
-
1. **Authorization Request**
|
145
|
-
```ruby
|
146
|
-
def authorize_url(state:, nonce:, scope:)
|
147
|
-
params = {
|
148
|
-
response_type: "code",
|
149
|
-
client_id: client_id,
|
150
|
-
redirect_uri: redirect_uri,
|
151
|
-
scope: scope || default_scopes,
|
152
|
-
state: state
|
153
|
-
}
|
103
|
+
# app/models/concerns/clavis_user_methods.rb
|
104
|
+
def find_or_create_from_clavis(auth_hash)
|
105
|
+
# First try to find existing identity...
|
154
106
|
|
155
|
-
#
|
156
|
-
|
107
|
+
# Create new user if none exists
|
108
|
+
if user.nil?
|
109
|
+
# Convert to HashWithIndifferentAccess for reliable key access
|
110
|
+
info = auth_hash[:info].with_indifferent_access if auth_hash[:info]
|
111
|
+
|
112
|
+
user = new(
|
113
|
+
email: info&.dig(:email),
|
114
|
+
# Add your required User model fields here:
|
115
|
+
first_name: info&.dig(:given_name) || info&.dig(:first_name),
|
116
|
+
last_name: info&.dig(:family_name) || info&.dig(:last_name),
|
117
|
+
username: info&.dig(:nickname) || "user_#{SecureRandom.hex(4)}",
|
118
|
+
avatar_url: info&.dig(:picture) || info&.dig(:image),
|
119
|
+
terms_accepted: true # For required boolean fields
|
120
|
+
)
|
121
|
+
|
122
|
+
# Mark this user for password validation skipping
|
123
|
+
user.skip_password_validation = true
|
124
|
+
user.save!
|
125
|
+
end
|
157
126
|
|
158
|
-
|
159
|
-
end
|
160
|
-
```
|
161
|
-
|
162
|
-
2. **Authorization Callback**
|
163
|
-
```ruby
|
164
|
-
def oauth_callback
|
165
|
-
validate_state!(params[:state])
|
166
|
-
auth_hash = provider.process_callback(params[:code], session.delete(:oauth_state))
|
167
|
-
user = find_or_create_user_from_oauth(auth_hash)
|
168
|
-
yield(user, auth_hash) if block_given?
|
127
|
+
# Create identity and return user...
|
169
128
|
end
|
170
129
|
```
|
171
130
|
|
172
|
-
|
173
|
-
```ruby
|
174
|
-
def token_exchange(code:, expected_state: nil)
|
175
|
-
response = http_client.post(token_endpoint, {
|
176
|
-
grant_type: "authorization_code",
|
177
|
-
code: code,
|
178
|
-
redirect_uri: redirect_uri,
|
179
|
-
client_id: client_id,
|
180
|
-
client_secret: client_secret
|
181
|
-
})
|
182
|
-
|
183
|
-
handle_token_response(response)
|
184
|
-
end
|
185
|
-
```
|
131
|
+
## Auth Hash Structure
|
186
132
|
|
187
|
-
|
133
|
+
The `auth_hash` contains standardized OAuth data from providers:
|
188
134
|
|
189
135
|
```ruby
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
end
|
194
|
-
|
195
|
-
def token_endpoint
|
196
|
-
"https://oauth2.googleapis.com/token"
|
197
|
-
end
|
198
|
-
|
199
|
-
def userinfo_endpoint
|
200
|
-
"https://openidconnect.googleapis.com/v1/userinfo"
|
201
|
-
end
|
136
|
+
{
|
137
|
+
provider: "google", # Provider name (string)
|
138
|
+
uid: "123456789", # Unique user ID (string)
|
202
139
|
|
203
|
-
|
204
|
-
|
205
|
-
|
140
|
+
info: { # User information
|
141
|
+
email: "user@example.com",
|
142
|
+
email_verified: true, # Only from OIDC providers
|
143
|
+
name: "John Doe",
|
144
|
+
given_name: "John", # From Google/OIDC
|
145
|
+
family_name: "Doe", # From Google/OIDC
|
146
|
+
first_name: "John", # From some OAuth2 providers
|
147
|
+
last_name: "Doe", # From some OAuth2 providers
|
148
|
+
nickname: "johndoe", # Usually from GitHub
|
149
|
+
picture: "https://...", # From Google/OIDC
|
150
|
+
image: "https://...", # From OAuth2 providers
|
151
|
+
urls: { # Provider-specific profile URLs
|
152
|
+
website: "https://...",
|
153
|
+
profile: "https://..."
|
154
|
+
}
|
155
|
+
},
|
206
156
|
|
207
|
-
|
208
|
-
|
209
|
-
|
157
|
+
credentials: { # OAuth tokens
|
158
|
+
token: "ACCESS_TOKEN",
|
159
|
+
refresh_token: "REFRESH_TOKEN",
|
160
|
+
expires_at: 1494520494, # Unix timestamp
|
161
|
+
expires: true # Whether token expires
|
162
|
+
},
|
210
163
|
|
211
|
-
|
164
|
+
id_token_claims: { # OpenID Connect claims (Google, Microsoft)
|
165
|
+
sub: "123456789", # Stable unique identifier
|
166
|
+
email: "user@example.com",
|
167
|
+
email_verified: true,
|
168
|
+
name: "John Doe",
|
169
|
+
picture: "https://..."
|
170
|
+
},
|
212
171
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
# For OpenID Connect providers like Google, we use the sub claim
|
217
|
-
# as the stable identifier. This is guaranteed to be unique and
|
218
|
-
# consistent for each user, unlike other fields that might change.
|
219
|
-
{
|
220
|
-
provider: "google",
|
221
|
-
uid: data["sub"], # sub is the stable identifier
|
222
|
-
info: {
|
223
|
-
email: data["email"],
|
224
|
-
name: data["name"],
|
225
|
-
image: data["picture"]
|
226
|
-
}
|
227
|
-
}
|
228
|
-
end
|
229
|
-
end
|
230
|
-
```
|
231
|
-
|
232
|
-
### OpenID Connect vs OAuth2 Providers
|
233
|
-
|
234
|
-
Clavis handles two types of providers differently:
|
235
|
-
|
236
|
-
1. **OpenID Connect Providers** (e.g., Google)
|
237
|
-
- Uses the `sub` claim as the stable identifier
|
238
|
-
- This is guaranteed to be unique and consistent
|
239
|
-
- Found in the ID token claims or userinfo response
|
240
|
-
- Example: Google's `sub` is a stable numeric identifier
|
241
|
-
|
242
|
-
2. **OAuth2-only Providers** (e.g., GitHub)
|
243
|
-
- Uses the provider's `uid` field
|
244
|
-
- Identifier format varies by provider
|
245
|
-
- Example: GitHub uses the user's numeric ID
|
246
|
-
|
247
|
-
When implementing a custom provider, use `openid_provider?` to indicate if it's an OpenID Connect provider:
|
248
|
-
|
249
|
-
```ruby
|
250
|
-
def openid_provider?
|
251
|
-
true # for OIDC providers
|
252
|
-
false # for OAuth2-only providers
|
253
|
-
end
|
254
|
-
```
|
255
|
-
|
256
|
-
## Usage Examples
|
257
|
-
|
258
|
-
### Basic Setup
|
259
|
-
|
260
|
-
```ruby
|
261
|
-
# Gemfile
|
262
|
-
gem 'clavis'
|
263
|
-
|
264
|
-
# Terminal
|
265
|
-
bundle install
|
266
|
-
rails g clavis:install
|
267
|
-
|
268
|
-
# config/initializers/clavis.rb
|
269
|
-
Clavis.configure do |config|
|
270
|
-
config.providers = {
|
271
|
-
google: {
|
272
|
-
client_id: Rails.application.credentials.dig(:google, :client_id),
|
273
|
-
client_secret: Rails.application.credentials.dig(:google, :client_secret),
|
274
|
-
redirect_uri: "https://example.com/auth/google/callback"
|
275
|
-
}
|
172
|
+
extra: { # Additional provider data
|
173
|
+
raw_info: { /* Raw provider response */ }
|
276
174
|
}
|
277
|
-
|
278
|
-
|
279
|
-
# app/models/user.rb
|
280
|
-
class User < ApplicationRecord
|
281
|
-
include Clavis::Models::OauthAuthenticatable
|
282
|
-
end
|
175
|
+
}
|
283
176
|
```
|
284
177
|
|
285
178
|
### Accessing User Info
|
286
179
|
|
287
180
|
```ruby
|
288
|
-
# Get
|
181
|
+
# Get info from most recent OAuth provider
|
289
182
|
user.oauth_email # => "user@example.com"
|
290
183
|
user.oauth_name # => "John Doe"
|
291
184
|
user.oauth_avatar_url # => "https://example.com/avatar.jpg"
|
292
185
|
|
293
186
|
# Get info from specific provider
|
294
187
|
user.oauth_email("google")
|
188
|
+
user.oauth_name("github")
|
189
|
+
|
190
|
+
# Check if OAuth user
|
191
|
+
user.oauth_user? # => true/false
|
295
192
|
```
|
296
193
|
|
297
|
-
|
194
|
+
## Provider Integration Examples
|
195
|
+
|
196
|
+
### Controller Example
|
298
197
|
|
299
198
|
```ruby
|
300
199
|
class SessionsController < ApplicationController
|
@@ -306,7 +205,7 @@ class SessionsController < ApplicationController
|
|
306
205
|
redirect_to dashboard_path
|
307
206
|
end
|
308
207
|
rescue Clavis::AuthenticationError => e
|
309
|
-
redirect_to login_path, alert: "Authentication failed"
|
208
|
+
redirect_to login_path, alert: "Authentication failed: #{e.message}"
|
310
209
|
end
|
311
210
|
end
|
312
211
|
```
|
@@ -314,98 +213,163 @@ end
|
|
314
213
|
### View Integration
|
315
214
|
|
316
215
|
```erb
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
216
|
+
<!-- Basic buttons -->
|
217
|
+
<%= clavis_oauth_button :google %>
|
218
|
+
<%= clavis_oauth_button :github %>
|
219
|
+
|
220
|
+
<!-- Customized buttons -->
|
221
|
+
<%= clavis_oauth_button :google, text: "Continue with Google" %>
|
222
|
+
<%= clavis_oauth_button :github, class: "my-custom-button" %>
|
223
|
+
<%= clavis_oauth_button :apple, html: { data: { turbo: false } } %>
|
322
224
|
```
|
323
225
|
|
324
|
-
|
226
|
+
## Available Providers
|
227
|
+
|
228
|
+
| Provider | Key | Scopes | Identifier Strategy | Notes |
|
229
|
+
|------------|------------|------------------------|----------------------|-------|
|
230
|
+
| Google | `:google` | `openid email profile` | OIDC `sub` claim | Full OIDC support |
|
231
|
+
| GitHub | `:github` | `user:email` | OAuth2 `uid` | Uses GitHub API |
|
232
|
+
| Apple | `:apple` | `name email` | OIDC `sub` claim | JWT client secret |
|
233
|
+
| Facebook | `:facebook`| `email public_profile` | OAuth2 `uid` | Uses Graph API |
|
234
|
+
| Microsoft | `:microsoft`| `openid email profile` | OIDC `sub` claim | Multi-tenant support |
|
235
|
+
|
236
|
+
## Password Integration
|
237
|
+
|
238
|
+
For User models with `has_secure_password`, handle password validation:
|
325
239
|
|
326
240
|
```ruby
|
327
|
-
# app/
|
328
|
-
|
329
|
-
include
|
241
|
+
# app/models/user.rb
|
242
|
+
class User < ApplicationRecord
|
243
|
+
include ClavisUserMethods
|
244
|
+
has_secure_password
|
245
|
+
|
246
|
+
# Option 1: Skip validation for OAuth users (recommended)
|
247
|
+
validates :password, presence: true,
|
248
|
+
unless: -> { skip_password_validation }, on: :create
|
249
|
+
|
250
|
+
# Option 2: Set random password for OAuth users
|
251
|
+
before_validation :set_random_password,
|
252
|
+
if: -> { skip_password_validation && respond_to?(:password=) }
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
def set_random_password
|
257
|
+
self.password = SecureRandom.hex(16)
|
258
|
+
self.password_confirmation = password if respond_to?(:password_confirmation=)
|
259
|
+
end
|
330
260
|
end
|
331
261
|
```
|
332
262
|
|
333
|
-
|
263
|
+
## Common Errors and Solutions
|
334
264
|
|
335
|
-
|
265
|
+
| Error | Cause | Solution | Code Example |
|
266
|
+
|-------|-------|----------|--------------|
|
267
|
+
| `redirect_uri_mismatch` | URI in code doesn't match provider console | Make URIs identical (protocol, domain, port, path) | Check both provider config and console settings |
|
268
|
+
| `invalid_client` | Client ID/secret incorrect | Check provider credentials in config | Verify ENV variables are correctly set |
|
269
|
+
| `User validation failed` | Required fields missing | Customize user creation with required fields | See [User Creation](#critical-step-customize-user-creation) |
|
270
|
+
| `Password can't be blank` | Password validation for OAuth users | Implement validation skipping for OAuth users | See [Password Integration](#password-integration) |
|
271
|
+
| `unknown provider` | Provider not configured | Add provider to configuration | Add to `config.providers` hash |
|
272
|
+
| `undefined method user for nil` | OAuth identity not associated with user | Fix user creation process | Check `find_or_create_from_clavis` implementation |
|
336
273
|
|
337
|
-
|
338
|
-
<!-- Custom text -->
|
339
|
-
<%= clavis_oauth_button :google, text: "Continue with Google" %>
|
274
|
+
## Error Handling Implementation
|
340
275
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
276
|
+
```ruby
|
277
|
+
# In your controllers
|
278
|
+
def oauth_callback
|
279
|
+
begin
|
280
|
+
# Standard OAuth flow
|
281
|
+
auth_hash = process_callback(params[:provider])
|
282
|
+
user = User.find_or_create_from_clavis(auth_hash)
|
283
|
+
sign_in_user(user)
|
284
|
+
redirect_to after_sign_in_path
|
285
|
+
rescue Clavis::Error => e
|
286
|
+
case e.message
|
287
|
+
when /redirect_uri_mismatch/
|
288
|
+
redirect_to sign_in_path, alert: "OAuth configuration error. Please contact support."
|
289
|
+
when /invalid_client/
|
290
|
+
redirect_to sign_in_path, alert: "Authentication service unavailable."
|
291
|
+
when /unknown provider/
|
292
|
+
redirect_to sign_in_path, alert: "This login method is not available."
|
293
|
+
else
|
294
|
+
redirect_to sign_in_path, alert: "Authentication failed: #{e.message}"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
346
298
|
```
|
347
299
|
|
348
|
-
|
349
|
-
|
350
|
-
## Customization Guide
|
351
|
-
|
352
|
-
### Adding a Custom Provider
|
300
|
+
## Custom Provider Example
|
353
301
|
|
354
302
|
```ruby
|
355
|
-
|
303
|
+
# config/initializers/clavis.rb
|
304
|
+
class CustomProvider < Clavis::Providers::Base
|
356
305
|
def authorization_endpoint
|
357
|
-
"https://custom-provider.com/oauth/authorize"
|
306
|
+
"https://auth.custom-provider.com/oauth/authorize"
|
358
307
|
end
|
359
308
|
|
360
309
|
def token_endpoint
|
361
|
-
"https://custom-provider.com/oauth/token"
|
310
|
+
"https://auth.custom-provider.com/oauth/token"
|
362
311
|
end
|
363
312
|
|
364
313
|
def userinfo_endpoint
|
365
|
-
"https://custom-provider.com/
|
314
|
+
"https://api.custom-provider.com/userinfo"
|
315
|
+
end
|
316
|
+
|
317
|
+
def default_scopes
|
318
|
+
"email profile"
|
319
|
+
end
|
320
|
+
|
321
|
+
def openid_provider?
|
322
|
+
false # true for OIDC providers
|
366
323
|
end
|
367
324
|
end
|
368
325
|
|
369
|
-
# Register
|
326
|
+
# Register provider
|
370
327
|
Clavis.register_provider(:custom_provider, CustomProvider)
|
371
|
-
```
|
372
|
-
|
373
|
-
### Custom Claims Processing
|
374
328
|
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
user.add_role(:employee)
|
385
|
-
end
|
329
|
+
# Configure provider
|
330
|
+
Clavis.configure do |config|
|
331
|
+
config.providers = {
|
332
|
+
custom_provider: {
|
333
|
+
client_id: ENV["CUSTOM_CLIENT_ID"],
|
334
|
+
client_secret: ENV["CUSTOM_CLIENT_SECRET"],
|
335
|
+
redirect_uri: "https://your-app.com/auth/custom_provider/callback"
|
336
|
+
}
|
337
|
+
}
|
386
338
|
end
|
387
339
|
```
|
388
340
|
|
389
|
-
##
|
341
|
+
## OpenID Connect vs OAuth2
|
390
342
|
|
391
|
-
|
343
|
+
| Feature | OIDC Providers (Google, Microsoft, Apple) | OAuth2 Providers (GitHub, Facebook) |
|
344
|
+
|---------|-------------------------------------------|-------------------------------------|
|
345
|
+
| User identifier | `sub` claim (stable, guaranteed unique) | `uid` field (provider-specific) |
|
346
|
+
| Email verification | Provides `email_verified` claim | Usually not available |
|
347
|
+
| User info format | Standardized claims | Varies by provider |
|
348
|
+
| ID tokens | Provides JWT ID tokens | Not available |
|
349
|
+
| Access method | `auth_hash[:id_token_claims][:sub]` | `auth_hash[:uid]` |
|
350
|
+
| Example providers | Google, Microsoft, Apple | GitHub, Facebook |
|
392
351
|
|
393
|
-
|
394
|
-
2. **Nonce Parameter** - Prevents replay attacks for OIDC
|
395
|
-
3. **HTTPS** - Required for OAuth operations
|
396
|
-
4. **Secure Token Storage** - Encrypted in database
|
397
|
-
5. **Error Logging** - Security events monitoring
|
352
|
+
## Security Features
|
398
353
|
|
399
|
-
|
354
|
+
| Feature | Implementation | Purpose |
|
355
|
+
|---------|---------------|---------|
|
356
|
+
| CSRF Protection | State parameter | Prevents cross-site request forgery |
|
357
|
+
| Replay Prevention | Nonce parameter | Prevents token replay attacks |
|
358
|
+
| Transport Security | HTTPS requirement | Ensures secure data transmission |
|
359
|
+
| Token Encryption | Database encryption | Protects stored tokens |
|
360
|
+
| Rate Limiting | Request throttling | Protects against brute force/DDoS |
|
400
361
|
|
401
|
-
|
362
|
+
### Rate Limiting Configuration
|
402
363
|
|
403
364
|
```ruby
|
404
|
-
#
|
365
|
+
# Enabled by default with these rate limits:
|
366
|
+
# - Auth endpoints: 20 requests/minute per IP
|
367
|
+
# - Callback endpoints: 15 requests/minute per IP
|
368
|
+
# - Login attempts: 5 requests/20 seconds per email
|
369
|
+
|
370
|
+
# Custom configuration
|
405
371
|
Clavis.configure do |config|
|
406
372
|
config.rate_limiting_enabled = true
|
407
|
-
|
408
|
-
# Optional: Configure custom throttles
|
409
373
|
config.custom_throttles = {
|
410
374
|
"login_page": {
|
411
375
|
limit: 30,
|
@@ -416,72 +380,17 @@ Clavis.configure do |config|
|
|
416
380
|
end
|
417
381
|
```
|
418
382
|
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
#### Custom Configuration
|
435
|
-
|
436
|
-
For advanced customization, create a dedicated Rack::Attack configuration:
|
437
|
-
|
438
|
-
```ruby
|
439
|
-
# config/initializers/rack_attack.rb
|
440
|
-
Rack::Attack.throttle("custom/auth", limit: 10, period: 30.seconds) do |req|
|
441
|
-
req.ip if req.path =~ %r{/auth/}
|
442
|
-
end
|
443
|
-
|
444
|
-
# Dedicated cache store for rate limiting
|
445
|
-
Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(
|
446
|
-
url: ENV["REDIS_RATE_LIMIT_URL"]
|
447
|
-
)
|
448
|
-
```
|
449
|
-
|
450
|
-
#### Implementation Notes
|
451
|
-
|
452
|
-
1. Rate limiting middleware installation happens in `Clavis::Engine`
|
453
|
-
2. Throttle rules are defined in `Clavis::Security::RateLimiter`
|
454
|
-
3. Configuration via `rate_limiting_enabled` and `custom_throttles` in Clavis config
|
455
|
-
4. When disabled, no middleware is added and there's zero performance impact
|
456
|
-
|
457
|
-
## API Reference
|
458
|
-
|
459
|
-
### Available Providers
|
460
|
-
|
461
|
-
| Provider | Key | Default Scopes | Notes |
|
462
|
-
|----------|-----|----------------|-------|
|
463
|
-
| Google | `:google` | `openid email profile` | Full OIDC support |
|
464
|
-
| GitHub | `:github` | `user:email` | Uses GitHub API |
|
465
|
-
| Apple | `:apple` | `name email` | JWT client secret |
|
466
|
-
| Facebook | `:facebook` | `email public_profile` | Uses Graph API |
|
467
|
-
| Microsoft | `:microsoft` | `openid email profile` | Multi-tenant |
|
468
|
-
|
469
|
-
### Auth Hash Structure
|
470
|
-
|
471
|
-
```ruby
|
472
|
-
{
|
473
|
-
provider: "google",
|
474
|
-
uid: "123456789",
|
475
|
-
info: {
|
476
|
-
email: "user@example.com",
|
477
|
-
email_verified: true,
|
478
|
-
name: "John Doe",
|
479
|
-
image: "https://example.com/photo.jpg"
|
480
|
-
},
|
481
|
-
credentials: {
|
482
|
-
token: "ACCESS_TOKEN",
|
483
|
-
refresh_token: "REFRESH_TOKEN",
|
484
|
-
expires_at: 1494520494
|
485
|
-
}
|
486
|
-
}
|
487
|
-
```
|
383
|
+
## Environment Variables Summary
|
384
|
+
|
385
|
+
| Variable | Purpose | Format | Required |
|
386
|
+
|----------|---------|--------|----------|
|
387
|
+
| GOOGLE_CLIENT_ID | Google OAuth | String | For Google auth |
|
388
|
+
| GOOGLE_CLIENT_SECRET | Google OAuth | String | For Google auth |
|
389
|
+
| GITHUB_CLIENT_ID | GitHub OAuth | String | For GitHub auth |
|
390
|
+
| GITHUB_CLIENT_SECRET | GitHub OAuth | String | For GitHub auth |
|
391
|
+
| APPLE_CLIENT_ID | Apple OAuth | String | For Apple auth |
|
392
|
+
| APPLE_CLIENT_SECRET | Apple OAuth | JWT/PEM | For Apple auth |
|
393
|
+
| FACEBOOK_CLIENT_ID | Facebook OAuth | String | For Facebook auth |
|
394
|
+
| FACEBOOK_CLIENT_SECRET | Facebook OAuth | String | For Facebook auth |
|
395
|
+
| MICROSOFT_CLIENT_ID | Microsoft OAuth | String | For Microsoft auth |
|
396
|
+
| MICROSOFT_CLIENT_SECRET | Microsoft OAuth | String | For Microsoft auth |
|