clavis 0.7.1 → 0.8.0
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 +374 -535
- data/Rakefile +52 -4
- data/gemfiles/rails_80.gemfile +1 -0
- data/lib/clavis/controllers/concerns/authentication.rb +183 -113
- data/lib/clavis/errors.rb +19 -0
- data/lib/clavis/logging.rb +53 -0
- data/lib/clavis/providers/apple.rb +249 -15
- data/lib/clavis/providers/base.rb +163 -75
- data/lib/clavis/providers/facebook.rb +123 -10
- data/lib/clavis/providers/github.rb +47 -10
- data/lib/clavis/providers/google.rb +189 -6
- data/lib/clavis/providers/token_exchange_handler.rb +125 -0
- data/lib/clavis/security/csrf_protection.rb +92 -8
- data/lib/clavis/security/session_manager.rb +10 -0
- data/lib/clavis/version.rb +1 -1
- data/lib/clavis.rb +5 -0
- data/lib/generators/clavis/install_generator.rb +34 -17
- data/lib/generators/clavis/templates/initializer.rb +4 -2
- data/lib/generators/clavis/user_method/user_method_generator.rb +5 -16
- data/llms.md +256 -347
- metadata +4 -5
- data/UPGRADE.md +0 -57
- data/docs/integration.md +0 -272
- data/testing_plan.md +0 -710
data/README.md
CHANGED
@@ -8,36 +8,37 @@ You should be able to install and go in 5 minutes.
|
|
8
8
|
|
9
9
|
> 🔑 **Fun fact**: The name "Clavis" comes from the Latin word for "key" - a fitting name for a gem that unlocks secure authentication!
|
10
10
|
|
11
|
+
## Assumptions
|
12
|
+
|
13
|
+
Before installing Clavis, note these assumptions:
|
14
|
+
|
15
|
+
1. You're using 8+
|
16
|
+
2. You've got a User model (maybe has_secure_password, maybe not)
|
17
|
+
3. You want speed over configuration flexibility
|
18
|
+
|
11
19
|
## Quick Start Guide
|
12
20
|
|
13
|
-
Get up and running with OAuth authentication in
|
21
|
+
Get up and running with OAuth authentication in these simple steps:
|
22
|
+
|
23
|
+
### Step 1: Installation
|
14
24
|
|
15
25
|
```ruby
|
16
|
-
#
|
26
|
+
# Add to your Gemfile and run bundle install
|
17
27
|
gem 'clavis'
|
18
28
|
```
|
19
29
|
|
20
30
|
```bash
|
21
|
-
#
|
22
|
-
# This automatically:
|
23
|
-
# - Creates the necessary migrations
|
24
|
-
# - Creates a configuration initializer
|
25
|
-
# - Adds OAuth fields to your User model
|
26
|
-
# - Mounts the engine at '/auth' in routes.rb
|
31
|
+
# Run the installation generator
|
27
32
|
rails generate clavis:install
|
28
33
|
rails db:migrate
|
29
34
|
```
|
30
35
|
|
36
|
+
### Step 2: Configuration
|
37
|
+
|
31
38
|
```ruby
|
32
|
-
#
|
33
|
-
# The generator created this file for you - just update with your credentials
|
39
|
+
# Configure a provider in config/initializers/clavis.rb
|
34
40
|
Clavis.configure do |config|
|
35
41
|
config.providers = {
|
36
|
-
google: {
|
37
|
-
client_id: ENV['GOOGLE_CLIENT_ID'],
|
38
|
-
client_secret: ENV['GOOGLE_CLIENT_SECRET'],
|
39
|
-
redirect_uri: 'https://your-app.com/auth/google/callback'
|
40
|
-
},
|
41
42
|
github: {
|
42
43
|
client_id: ENV['GITHUB_CLIENT_ID'],
|
43
44
|
client_secret: ENV['GITHUB_CLIENT_SECRET'],
|
@@ -47,11 +48,56 @@ Clavis.configure do |config|
|
|
47
48
|
end
|
48
49
|
```
|
49
50
|
|
50
|
-
|
51
|
+
### Step 3: Add OAuth Button
|
52
|
+
|
51
53
|
```erb
|
54
|
+
<!-- Add to your login view -->
|
52
55
|
<%= clavis_oauth_button :github %>
|
53
56
|
```
|
54
57
|
|
58
|
+
### Step 4: ⚠️ IMPORTANT - Customize User Creation
|
59
|
+
|
60
|
+
You MUST customize the user creation code to include all required fields for your User model:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# Edit app/models/concerns/clavis_user_methods.rb
|
64
|
+
def find_or_create_from_clavis(auth_hash)
|
65
|
+
# ... existing identity lookup code ...
|
66
|
+
|
67
|
+
if user.nil?
|
68
|
+
info = auth_hash[:info].with_indifferent_access if auth_hash[:info]
|
69
|
+
|
70
|
+
user = new(
|
71
|
+
email: info&.dig(:email),
|
72
|
+
# Add your required fields here:
|
73
|
+
first_name: info&.dig(:given_name) || info&.dig(:first_name),
|
74
|
+
last_name: info&.dig(:family_name) || info&.dig(:last_name)
|
75
|
+
# Add other required fields specific to your User model
|
76
|
+
)
|
77
|
+
|
78
|
+
user.skip_password_validation = true
|
79
|
+
user.save!
|
80
|
+
end
|
81
|
+
|
82
|
+
# ... create identity and return user ...
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
### For Password-Protected Users
|
87
|
+
|
88
|
+
If your User model uses `has_secure_password`, handle password validation:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
# app/models/user.rb
|
92
|
+
class User < ApplicationRecord
|
93
|
+
include ClavisUserMethods
|
94
|
+
|
95
|
+
# Skip password validation for OAuth users
|
96
|
+
validates :password, presence: true,
|
97
|
+
unless: -> { skip_password_validation }, on: :create
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
55
101
|
That's it! You now have "Sign in with GitHub" functionality in your Rails app.
|
56
102
|
|
57
103
|
If you are an AI Agent or LLM, read this: [llms.md](llms.md)
|
@@ -61,40 +107,26 @@ If you are a human, who likes reading documentation, and you recently touched gr
|
|
61
107
|
## Table of Contents
|
62
108
|
|
63
109
|
1. [Assumptions](#assumptions)
|
64
|
-
2. [
|
65
|
-
3. [
|
66
|
-
4. [
|
67
|
-
5. [
|
68
|
-
6. [
|
69
|
-
7. [
|
70
|
-
8. [
|
71
|
-
9. [
|
72
|
-
10. [
|
73
|
-
11. [
|
74
|
-
12. [
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
17. [Development](#development)
|
80
|
-
18. [Contributing](#contributing)
|
81
|
-
19. [License](#license)
|
82
|
-
20. [Code of Conduct](#code-of-conduct)
|
83
|
-
|
84
|
-
## Assumptions
|
85
|
-
|
86
|
-
Before installing Clavis, note these assumptions:
|
87
|
-
|
88
|
-
1. You're using Rails 7+
|
89
|
-
2. You've got a User model and some form of authentication already
|
90
|
-
3. You want speed over configuration flexibility
|
91
|
-
|
92
|
-
## Installation
|
110
|
+
2. [Quick Start Guide](#quick-start-guide)
|
111
|
+
3. [Installation & Setup](#installation--setup)
|
112
|
+
4. [Configuration](#configuration)
|
113
|
+
5. [User Management](#user-management)
|
114
|
+
6. [View Integration](#view-integration)
|
115
|
+
7. [Advanced Features](#advanced-features)
|
116
|
+
8. [Provider Setup](#provider-setup)
|
117
|
+
9. [Security & Rate Limiting](#security--rate-limiting)
|
118
|
+
10. [Troubleshooting](#troubleshooting)
|
119
|
+
11. [Development](#development)
|
120
|
+
12. [Contributing](#contributing)
|
121
|
+
|
122
|
+
## Installation & Setup
|
123
|
+
|
124
|
+
### Installation
|
93
125
|
|
94
126
|
Add to your Gemfile:
|
95
127
|
|
96
128
|
```ruby
|
97
|
-
gem 'clavis'
|
129
|
+
gem 'clavis'
|
98
130
|
```
|
99
131
|
|
100
132
|
Install and set up:
|
@@ -105,7 +137,35 @@ rails generate clavis:install
|
|
105
137
|
rails db:migrate
|
106
138
|
```
|
107
139
|
|
108
|
-
|
140
|
+
### Database Setup
|
141
|
+
|
142
|
+
The generator creates migrations for:
|
143
|
+
|
144
|
+
1. OAuth identities table
|
145
|
+
2. User model OAuth fields
|
146
|
+
|
147
|
+
### Routes Configuration
|
148
|
+
|
149
|
+
The generator mounts the engine:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
# config/routes.rb
|
153
|
+
mount Clavis::Engine => "/auth"
|
154
|
+
```
|
155
|
+
|
156
|
+
### Integrating with Existing Authentication
|
157
|
+
|
158
|
+
1. Configure as shown in the Quick Start
|
159
|
+
2. Run the generator
|
160
|
+
3. Include the module in your User model:
|
161
|
+
```ruby
|
162
|
+
# app/models/user.rb
|
163
|
+
include Clavis::Models::OauthAuthenticatable
|
164
|
+
```
|
165
|
+
|
166
|
+
## Configuration
|
167
|
+
|
168
|
+
### Basic Configuration
|
109
169
|
|
110
170
|
Configure in an initializer:
|
111
171
|
|
@@ -129,285 +189,243 @@ end
|
|
129
189
|
|
130
190
|
> ⚠️ **Important**: The `redirect_uri` must match EXACTLY what you've registered in the provider's developer console. If there's a mismatch, you'll get errors like "redirect_uri_mismatch". Pay attention to the protocol (http/https), domain, port, and path - all must match precisely.
|
131
191
|
|
132
|
-
|
192
|
+
### Configuration Options
|
133
193
|
|
134
|
-
|
194
|
+
See `config/initializers/clavis.rb` for all configuration options.
|
135
195
|
|
136
|
-
|
137
|
-
1. Go to [Google Cloud Console](https://console.cloud.google.com)
|
138
|
-
2. Navigate to "APIs & Services" > "Credentials"
|
139
|
-
3. Create or edit an OAuth 2.0 Client ID
|
140
|
-
4. Under "Authorized redirect URIs" add exactly the same URI as in your Clavis config:
|
141
|
-
- For development: `http://localhost:3000/auth/google/callback`
|
142
|
-
- For production: `https://your-app.com/auth/google/callback`
|
196
|
+
#### Verbose Logging
|
143
197
|
|
144
|
-
|
145
|
-
1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
|
146
|
-
2. Navigate to "OAuth Apps" and create or edit your app
|
147
|
-
3. In the "Authorization callback URL" field, add exactly the same URI as in your Clavis config
|
198
|
+
By default, Clavis keeps its logs minimal to avoid cluttering your application logs. If you need more detailed logs during authentication processes for debugging purposes, you can enable verbose logging:
|
148
199
|
|
149
|
-
|
150
|
-
|
151
|
-
|
200
|
+
```ruby
|
201
|
+
Clavis.configure do |config|
|
202
|
+
# Enable detailed authentication flow logs
|
203
|
+
config.verbose_logging = true
|
204
|
+
end
|
205
|
+
```
|
152
206
|
|
153
|
-
|
207
|
+
When enabled, this will log details about:
|
208
|
+
- Token exchanges
|
209
|
+
- User info requests
|
210
|
+
- Token refreshes and verifications
|
211
|
+
- Authorization requests and callbacks
|
154
212
|
|
155
|
-
|
213
|
+
This is particularly useful for debugging OAuth integration issues, but should typically be disabled in production.
|
156
214
|
|
157
|
-
|
158
|
-
2. User model OAuth fields
|
215
|
+
## User Management
|
159
216
|
|
160
|
-
|
217
|
+
Clavis delegates user creation and management to your application through the `find_or_create_from_clavis` method. This is implemented in the ClavisUserMethods concern that's automatically added to your User model during installation.
|
161
218
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
# app/models/user.rb
|
167
|
-
include Clavis::Models::OauthAuthenticatable
|
168
|
-
```
|
169
|
-
4. Add OAuth buttons to your login page:
|
170
|
-
```erb
|
171
|
-
<%= clavis_oauth_button :github, class: "oauth-button github" %>
|
172
|
-
<%= clavis_oauth_button :google, class: "oauth-button google" %>
|
173
|
-
```
|
219
|
+
The concern provides:
|
220
|
+
- Helper methods for accessing OAuth data
|
221
|
+
- Logic to create or find users based on OAuth data
|
222
|
+
- Support for skipping password validation for OAuth users
|
174
223
|
|
175
|
-
|
224
|
+
### The OauthIdentity Model
|
176
225
|
|
177
|
-
|
226
|
+
Clavis stores OAuth credentials and user information in a polymorphic `OauthIdentity` model. This model has a `belongs_to :authenticatable, polymorphic: true` relationship, allowing it to be associated with any type of user model.
|
227
|
+
|
228
|
+
For convenience, the model also provides `user` and `user=` methods that are aliases for `authenticatable` and `authenticatable=`:
|
178
229
|
|
179
230
|
```ruby
|
180
|
-
#
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
def oauth_authorize
|
185
|
-
redirect_to auth_url(params[:provider])
|
186
|
-
end
|
187
|
-
|
188
|
-
def oauth_callback
|
189
|
-
auth_hash = process_callback(params[:provider])
|
190
|
-
user = User.find_for_oauth(auth_hash)
|
191
|
-
session[:user_id] = user.id
|
192
|
-
redirect_to after_sign_in_path
|
193
|
-
rescue Clavis::Error => e
|
194
|
-
redirect_to sign_in_path, alert: "Authentication failed: #{e.message}"
|
195
|
-
end
|
196
|
-
|
197
|
-
private
|
198
|
-
|
199
|
-
def after_sign_in_path
|
200
|
-
stored_location || root_path
|
201
|
-
end
|
202
|
-
end
|
231
|
+
# These are equivalent:
|
232
|
+
identity.user = current_user
|
233
|
+
identity.authenticatable = current_user
|
203
234
|
```
|
204
235
|
|
205
|
-
|
236
|
+
This allows you to use `identity.user` in your code even though the underlying database uses the `authenticatable` columns.
|
206
237
|
|
207
|
-
|
238
|
+
#### Key features of the OauthIdentity model:
|
208
239
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
240
|
+
- Secure token storage (tokens are automatically encrypted/decrypted)
|
241
|
+
- User information stored in the `auth_data` JSON column
|
242
|
+
- Automatic token refresh capabilities
|
243
|
+
- Unique index on `provider` and `uid` to prevent duplicate identities
|
213
244
|
|
214
|
-
|
215
|
-
1. A `ClavisUserMethods` concern in `app/models/concerns/clavis_user_methods.rb`
|
216
|
-
2. Adds `include ClavisUserMethods` to your User model
|
245
|
+
### Integration with has_secure_password
|
217
246
|
|
218
|
-
The concern provides:
|
219
|
-
- Integration with the `OauthAuthenticatable` module for helper methods
|
220
|
-
- A `find_or_create_from_clavis` class method that handles user creation/lookup
|
221
|
-
- Conditional validation for password requirements (commented by default)
|
247
|
+
If your User model uses `has_secure_password` for authentication, you'll need to handle password validation carefully when creating users from OAuth. The generated ClavisUserMethods concern provides several strategies for dealing with this:
|
222
248
|
|
223
|
-
|
249
|
+
#### Option 1: Skip Password Validation (Recommended)
|
224
250
|
|
225
|
-
|
251
|
+
This approach adds a temporary attribute to mark OAuth users and skip password validation for them:
|
226
252
|
|
227
253
|
```ruby
|
228
|
-
#
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
)
|
237
|
-
else
|
238
|
-
Clavis::OauthIdentity.find_by(
|
239
|
-
provider: auth_hash[:provider],
|
240
|
-
uid: auth_hash[:uid]
|
241
|
-
)
|
242
|
-
end
|
243
|
-
return identity.user if identity&.user
|
244
|
-
|
245
|
-
# Finding existing user logic...
|
246
|
-
|
247
|
-
# Create new user if none exists
|
248
|
-
if user.nil?
|
249
|
-
# Convert hash data to HashWithIndifferentAccess for reliable key access
|
250
|
-
info = auth_hash[:info].with_indifferent_access if auth_hash[:info]
|
251
|
-
|
252
|
-
user = new(
|
253
|
-
email: info&.dig(:email)
|
254
|
-
# You MUST add other required fields for your User model here!
|
255
|
-
)
|
256
|
-
|
257
|
-
user.save!
|
258
|
-
end
|
259
|
-
|
260
|
-
# Create or update the OAuth identity...
|
254
|
+
# app/models/user.rb
|
255
|
+
class User < ApplicationRecord
|
256
|
+
include ClavisUserMethods
|
257
|
+
has_secure_password
|
258
|
+
|
259
|
+
# Skip password validation for OAuth users
|
260
|
+
validates :password, presence: true, length: { minimum: 8 },
|
261
|
+
unless: -> { skip_password_validation }, on: :create
|
261
262
|
end
|
262
263
|
```
|
263
264
|
|
264
|
-
|
265
|
-
|
266
|
-
For OpenID Connect providers (like Google), Clavis uses the `sub` claim from the ID token as the stable identifier. This is important because:
|
267
|
-
|
268
|
-
1. The `sub` claim is guaranteed to be unique and stable for each user
|
269
|
-
2. Other fields like `uid` might change between logins
|
270
|
-
3. This follows the OpenID Connect specification
|
271
|
-
|
272
|
-
For non-OpenID Connect providers (like GitHub), Clavis continues to use the `uid` field as the identifier.
|
265
|
+
The `skip_password_validation` attribute is set automatically in the OAuth flow.
|
273
266
|
|
274
|
-
|
267
|
+
#### Option 2: Set Random Password
|
275
268
|
|
276
|
-
|
269
|
+
Another approach is to set a random secure password for OAuth users:
|
277
270
|
|
278
271
|
```ruby
|
279
|
-
#
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
info[:email] # User's email address
|
284
|
-
info[:name] # User's full name
|
285
|
-
info[:given_name] # First name (Google)
|
286
|
-
info[:first_name] # First name (some providers)
|
287
|
-
info[:family_name] # Last name (Google)
|
288
|
-
info[:last_name] # Last name (some providers)
|
289
|
-
info[:nickname] # Username or handle
|
290
|
-
info[:picture] # Profile picture URL (Google)
|
291
|
-
info[:image] # Profile picture URL (some providers)
|
292
|
-
```
|
293
|
-
|
294
|
-
Example of customized user creation:
|
272
|
+
# app/models/user.rb
|
273
|
+
class User < ApplicationRecord
|
274
|
+
include ClavisUserMethods
|
275
|
+
has_secure_password
|
295
276
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
)
|
277
|
+
# Set a random password for OAuth users
|
278
|
+
before_validation :set_random_password,
|
279
|
+
if: -> { skip_password_validation && respond_to?(:password=) }
|
280
|
+
|
281
|
+
private
|
282
|
+
|
283
|
+
def set_random_password
|
284
|
+
self.password = SecureRandom.hex(16)
|
285
|
+
self.password_confirmation = password if respond_to?(:password_confirmation=)
|
286
|
+
end
|
287
|
+
end
|
308
288
|
```
|
309
289
|
|
310
|
-
|
290
|
+
#### Option 3: Bypass Validations (Use with Caution)
|
311
291
|
|
312
|
-
|
292
|
+
As a last resort, you can bypass validations entirely when creating OAuth users:
|
313
293
|
|
314
294
|
```ruby
|
315
|
-
#
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
user.
|
321
|
-
|
295
|
+
# In app/models/concerns/clavis_user_methods.rb
|
296
|
+
def self.find_or_create_from_clavis(auth_hash)
|
297
|
+
# ... existing code ...
|
298
|
+
|
299
|
+
# Create a new user if none exists
|
300
|
+
if user.nil?
|
301
|
+
# ... set user attributes ...
|
302
|
+
|
303
|
+
# Bypass validations
|
304
|
+
user.save(validate: false)
|
305
|
+
end
|
306
|
+
|
307
|
+
# ... remainder of method ...
|
308
|
+
end
|
322
309
|
```
|
323
310
|
|
324
|
-
|
311
|
+
This approach isn't recommended as it might bypass important validations, but can be necessary in complex scenarios.
|
325
312
|
|
326
|
-
|
313
|
+
#### Database Setup
|
314
|
+
|
315
|
+
The Clavis generator automatically adds an `oauth_user` boolean field to your User model to help track which users were created through OAuth:
|
327
316
|
|
328
317
|
```ruby
|
329
|
-
#
|
330
|
-
|
318
|
+
# This is added automatically by the generator
|
319
|
+
add_column :users, :oauth_user, :boolean, default: false
|
331
320
|
```
|
332
321
|
|
333
|
-
This
|
334
|
-
1. Skip password requirements for OAuth users
|
335
|
-
2. Keep your regular password validations for non-OAuth users
|
336
|
-
3. Avoid storing useless random passwords in your database
|
322
|
+
This field is useful for conditional logic related to authentication methods.
|
337
323
|
|
338
|
-
###
|
324
|
+
### Session Management
|
339
325
|
|
340
|
-
|
326
|
+
Clavis handles user sessions through a concern module that is automatically included in your ApplicationController:
|
341
327
|
|
342
328
|
```ruby
|
343
|
-
#
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
329
|
+
# Available in your controllers after installation:
|
330
|
+
# include Clavis::Controllers::Concerns::Authentication
|
331
|
+
# include Clavis::Controllers::Concerns::SessionManagement
|
332
|
+
|
333
|
+
# Current user helper method
|
334
|
+
def current_user
|
335
|
+
@current_user ||= cookies.signed[:user_id] && User.find_by(id: cookies.signed[:user_id])
|
336
|
+
end
|
337
|
+
|
338
|
+
# Sign in helper
|
339
|
+
def sign_in_user(user)
|
340
|
+
cookies.signed[:user_id] = {
|
341
|
+
value: user.id,
|
342
|
+
httponly: true,
|
343
|
+
same_site: :lax,
|
344
|
+
secure: Rails.env.production?
|
345
|
+
}
|
350
346
|
end
|
351
347
|
```
|
352
348
|
|
349
|
+
#### Authentication Methods
|
350
|
+
|
351
|
+
The SessionManagement concern provides:
|
352
|
+
|
353
|
+
- `current_user` - Returns the currently authenticated user
|
354
|
+
- `authenticated?` - Returns whether a user is authenticated
|
355
|
+
- `sign_in_user(user)` - Signs in a user by setting a secure cookie
|
356
|
+
- `sign_out_user` - Signs out the current user
|
357
|
+
- `store_location` - Stores URL to return to after authentication
|
358
|
+
- `after_login_path` - Path to redirect to after login
|
359
|
+
- `after_logout_path` - Path to redirect to after logout
|
360
|
+
|
353
361
|
## View Integration
|
354
362
|
|
355
|
-
Include view helpers:
|
363
|
+
Include view helpers in your application:
|
356
364
|
|
357
365
|
```ruby
|
358
|
-
# app/helpers/
|
359
|
-
module
|
366
|
+
# app/helpers/application_helper.rb
|
367
|
+
module ApplicationHelper
|
360
368
|
include Clavis::ViewHelpers
|
361
369
|
end
|
362
370
|
```
|
363
371
|
|
364
|
-
###
|
365
|
-
|
366
|
-
The Clavis install generator will attempt to automatically add the required stylesheets to your application. If you need to manually include them:
|
367
|
-
|
368
|
-
For Sprockets (asset pipeline):
|
369
|
-
```css
|
370
|
-
/* app/assets/stylesheets/application.css */
|
371
|
-
/*
|
372
|
-
*= require clavis
|
373
|
-
*= require_self
|
374
|
-
*/
|
375
|
-
```
|
376
|
-
|
377
|
-
For Webpacker/Importmap:
|
378
|
-
```scss
|
379
|
-
/* app/assets/stylesheets/application.scss */
|
380
|
-
@import 'clavis';
|
381
|
-
```
|
382
|
-
|
383
|
-
### Using Buttons
|
372
|
+
### Using OAuth Buttons
|
384
373
|
|
385
|
-
|
374
|
+
Basic button usage:
|
386
375
|
|
387
376
|
```erb
|
388
377
|
<div class="oauth-buttons">
|
389
378
|
<%= clavis_oauth_button :google %>
|
390
379
|
<%= clavis_oauth_button :github %>
|
380
|
+
<%= clavis_oauth_button :microsoft %>
|
381
|
+
<%= clavis_oauth_button :facebook %>
|
382
|
+
<%= clavis_oauth_button :apple %>
|
391
383
|
</div>
|
392
384
|
```
|
393
385
|
|
394
|
-
|
386
|
+
Customizing buttons:
|
395
387
|
|
396
388
|
```erb
|
389
|
+
<!-- Custom text -->
|
397
390
|
<%= clavis_oauth_button :google, text: "Continue with Google" %>
|
391
|
+
|
392
|
+
<!-- Custom CSS class -->
|
398
393
|
<%= clavis_oauth_button :github, class: "my-custom-button" %>
|
394
|
+
|
395
|
+
<!-- Additional HTML attributes -->
|
396
|
+
<%= clavis_oauth_button :apple, html: { data: { turbo: false } } %>
|
397
|
+
|
398
|
+
<!-- All customization options -->
|
399
|
+
<%= clavis_oauth_button :github,
|
400
|
+
text: "Sign in via GitHub",
|
401
|
+
class: "custom-button github-button",
|
402
|
+
icon_class: "custom-icon",
|
403
|
+
html: { id: "github-login" } %>
|
399
404
|
```
|
400
405
|
|
401
|
-
|
406
|
+
The buttons come with built-in styles and brand-appropriate icons for the supported providers.
|
402
407
|
|
403
|
-
|
408
|
+
## Advanced Features
|
409
|
+
|
410
|
+
### Testing Your Integration
|
411
|
+
|
412
|
+
Access standardized user info:
|
404
413
|
|
405
414
|
```ruby
|
406
|
-
#
|
407
|
-
|
415
|
+
# From most recent OAuth provider
|
416
|
+
current_user.oauth_email
|
417
|
+
current_user.oauth_name
|
418
|
+
current_user.oauth_avatar_url
|
419
|
+
|
420
|
+
# From specific provider
|
421
|
+
current_user.oauth_email("google")
|
422
|
+
current_user.oauth_name("github")
|
423
|
+
|
424
|
+
# Check if OAuth user
|
425
|
+
current_user.oauth_user?
|
408
426
|
```
|
409
427
|
|
410
|
-
|
428
|
+
### Token Refresh
|
411
429
|
|
412
430
|
Provider support:
|
413
431
|
|
@@ -426,7 +444,7 @@ provider = Clavis.provider(:google, redirect_uri: "https://your-app.com/auth/goo
|
|
426
444
|
new_tokens = provider.refresh_token(oauth_identity.refresh_token)
|
427
445
|
```
|
428
446
|
|
429
|
-
|
447
|
+
### Custom Providers
|
430
448
|
|
431
449
|
Use the Generic provider:
|
432
450
|
|
@@ -466,22 +484,116 @@ end
|
|
466
484
|
Clavis.register_provider(:example_oauth, ExampleOAuth)
|
467
485
|
```
|
468
486
|
|
469
|
-
## Provider
|
487
|
+
## Provider Setup
|
488
|
+
|
489
|
+
### Setting Up OAuth Redirect URIs in Provider Consoles
|
470
490
|
|
471
|
-
|
491
|
+
When setting up OAuth, correctly configuring redirect URIs in both your app and the provider's developer console is crucial:
|
472
492
|
|
493
|
+
#### Google
|
494
|
+
1. Go to [Google Cloud Console](https://console.cloud.google.com)
|
495
|
+
2. Navigate to "APIs & Services" > "Credentials"
|
496
|
+
3. Create or edit an OAuth 2.0 Client ID
|
497
|
+
4. Under "Authorized redirect URIs" add exactly the same URI as in your Clavis config:
|
498
|
+
- For development: `http://localhost:3000/auth/google/callback`
|
499
|
+
- For production: `https://your-app.com/auth/google/callback`
|
500
|
+
|
501
|
+
#### GitHub
|
502
|
+
1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
|
503
|
+
2. Navigate to "OAuth Apps" and create or edit your app
|
504
|
+
3. In the "Authorization callback URL" field, add exactly the same URI as in your Clavis config
|
505
|
+
- For development: `http://localhost:3000/auth/github/callback`
|
506
|
+
- For production: `https://your-app.com/auth/github/callback`
|
507
|
+
|
508
|
+
#### Common Errors
|
509
|
+
- **Error 400: redirect_uri_mismatch** - This means the URI in your code doesn't match what's registered in the provider's console
|
510
|
+
- **Solution**: Ensure both URIs match exactly, including protocol (http/https), domain, port, and full path
|
511
|
+
|
512
|
+
#### GitHub Enterprise Support
|
513
|
+
|
514
|
+
Clavis supports GitHub Enterprise installations with custom configuration options:
|
515
|
+
|
516
|
+
```ruby
|
517
|
+
config.providers = {
|
518
|
+
github: {
|
519
|
+
client_id: ENV["GITHUB_CLIENT_ID"],
|
520
|
+
client_secret: ENV["GITHUB_CLIENT_SECRET"],
|
521
|
+
redirect_uri: "https://your-app.com/auth/github/callback",
|
522
|
+
# GitHub Enterprise settings:
|
523
|
+
site_url: "https://api.github.yourdomain.com", # Your Enterprise API endpoint
|
524
|
+
authorize_url: "https://github.yourdomain.com/login/oauth/authorize",
|
525
|
+
token_url: "https://github.yourdomain.com/login/oauth/access_token"
|
526
|
+
}
|
527
|
+
}
|
528
|
+
```
|
529
|
+
|
530
|
+
| Option | Description | Default |
|
531
|
+
|--------|-------------|---------|
|
532
|
+
| `site_url` | Base URL for the GitHub API | `https://api.github.com` |
|
533
|
+
| `authorize_url` | Authorization endpoint URL | `https://github.com/login/oauth/authorize` |
|
534
|
+
| `token_url` | Token exchange endpoint URL | `https://github.com/login/oauth/access_token` |
|
535
|
+
|
536
|
+
#### Facebook
|
537
|
+
1. Go to [Facebook Developer Portal](https://developers.facebook.com)
|
538
|
+
2. Create or select a Facebook app
|
539
|
+
3. Navigate to Settings > Basic to find your App ID and App Secret
|
540
|
+
4. Set up "Facebook Login" and configure "Valid OAuth Redirect URIs" with the exact URI from your Clavis config:
|
541
|
+
- For development: `http://localhost:3000/auth/facebook/callback`
|
542
|
+
- For production: `https://your-app.com/auth/facebook/callback`
|
543
|
+
|
544
|
+
### Provider Configuration Options
|
545
|
+
|
546
|
+
Providers can be configured with additional options for customizing behavior:
|
547
|
+
|
548
|
+
#### Facebook Provider Options
|
549
|
+
|
550
|
+
```ruby
|
551
|
+
config.providers = {
|
552
|
+
facebook: {
|
553
|
+
client_id: ENV["FACEBOOK_CLIENT_ID"],
|
554
|
+
client_secret: ENV["FACEBOOK_CLIENT_SECRET"],
|
555
|
+
redirect_uri: "https://your-app.com/auth/facebook/callback",
|
556
|
+
# Optional settings:
|
557
|
+
display: "popup", # Display mode - options: page, popup, touch
|
558
|
+
auth_type: "rerequest", # Auth type - useful for permission re-requests
|
559
|
+
image_size: "large", # Profile image size - small, normal, large, square
|
560
|
+
# Alternative: provide exact dimensions
|
561
|
+
image_size: { width: 200, height: 200 },
|
562
|
+
secure_image_url: true # Force HTTPS for image URLs (default true)
|
563
|
+
}
|
564
|
+
}
|
473
565
|
```
|
474
|
-
|
566
|
+
|
567
|
+
| Option | Description | Values | Default |
|
568
|
+
|--------|-------------|--------|---------|
|
569
|
+
| `display` | Controls how the authorization dialog is displayed | `page`, `popup`, `touch` | `page` |
|
570
|
+
| `auth_type` | Specifies the auth flow behavior | `rerequest`, `reauthenticate` | N/A |
|
571
|
+
| `image_size` | Profile image size | String: `small`, `normal`, `large`, `square` or Hash: `{ width: 200, height: 200 }` | N/A |
|
572
|
+
| `secure_image_url` | Force HTTPS for profile image URLs | `true`, `false` | `true` |
|
573
|
+
|
574
|
+
#### Using Facebook Long-Lived Tokens
|
575
|
+
|
576
|
+
Facebook access tokens are short-lived by default. The Facebook provider includes methods to exchange these for long-lived tokens:
|
577
|
+
|
578
|
+
```ruby
|
579
|
+
# Exchange a short-lived token for a long-lived token
|
580
|
+
provider = Clavis.provider(:facebook)
|
581
|
+
long_lived_token_data = provider.exchange_for_long_lived_token(oauth_identity.access_token)
|
582
|
+
|
583
|
+
# Update the OAuth identity with the new token
|
584
|
+
oauth_identity.update(
|
585
|
+
access_token: long_lived_token_data[:access_token],
|
586
|
+
expires_at: Time.now + long_lived_token_data[:expires_in].to_i.seconds
|
587
|
+
)
|
475
588
|
```
|
476
589
|
|
477
|
-
|
478
|
-
|
479
|
-
-
|
480
|
-
-
|
481
|
-
-
|
482
|
-
- [Microsoft](#microsoft)
|
590
|
+
#### Common Errors
|
591
|
+
|
592
|
+
- **Error 400: Invalid OAuth access token** - The token is invalid or expired
|
593
|
+
- **Error 400: redirect_uri does not match** - Mismatch between registered and provided redirect URI
|
594
|
+
- **Solution**: Ensure the redirect URI in your code matches exactly what's registered in Facebook Developer Portal
|
483
595
|
|
484
|
-
## Rate Limiting
|
596
|
+
## Security & Rate Limiting
|
485
597
|
|
486
598
|
Clavis includes built-in integration with the [Rack::Attack](https://github.com/rack/rack-attack) gem to protect your OAuth endpoints against DDoS and brute force attacks.
|
487
599
|
|
@@ -552,287 +664,14 @@ ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |name, start,
|
|
552
664
|
end
|
553
665
|
```
|
554
666
|
|
555
|
-
## Testing Your Integration
|
556
|
-
|
557
|
-
Access standardized user info:
|
558
|
-
|
559
|
-
```ruby
|
560
|
-
# From most recent OAuth provider
|
561
|
-
current_user.oauth_email
|
562
|
-
current_user.oauth_name
|
563
|
-
current_user.oauth_avatar_url
|
564
|
-
|
565
|
-
# From specific provider
|
566
|
-
current_user.oauth_email("google")
|
567
|
-
current_user.oauth_name("github")
|
568
|
-
|
569
|
-
# Check if OAuth user
|
570
|
-
current_user.oauth_user?
|
571
|
-
```
|
572
|
-
|
573
667
|
## Development
|
574
668
|
|
575
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake
|
669
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
576
670
|
|
577
671
|
The `rails-app` directory contains a Rails application used for integration testing and is not included in the gem package.
|
578
672
|
|
579
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
580
|
-
|
581
|
-
## Usage
|
582
|
-
|
583
|
-
### Basic Setup
|
584
|
-
|
585
|
-
1. Install the gem
|
586
|
-
2. Run the installation generator:
|
587
|
-
|
588
|
-
```
|
589
|
-
rails generate clavis:install
|
590
|
-
```
|
591
|
-
|
592
|
-
3. Configure your OAuth providers in `config/initializers/clavis.rb`:
|
593
|
-
|
594
|
-
```ruby
|
595
|
-
Clavis.configure do |config|
|
596
|
-
# Configure your OAuth providers
|
597
|
-
config.provider :github, client_id: "your-client-id", client_secret: "your-client-secret"
|
598
|
-
|
599
|
-
# Add other configurations as needed
|
600
|
-
end
|
601
|
-
```
|
602
|
-
|
603
|
-
4. Generate an authentication controller:
|
604
|
-
|
605
|
-
```
|
606
|
-
rails generate clavis:controller Auth
|
607
|
-
```
|
608
|
-
|
609
|
-
5. Add the routes to your application:
|
610
|
-
|
611
|
-
```ruby
|
612
|
-
# config/routes.rb
|
613
|
-
Rails.application.routes.draw do
|
614
|
-
get 'auth/:provider/callback', to: 'auth#callback'
|
615
|
-
get 'auth/failure', to: 'auth#failure'
|
616
|
-
get 'auth/:provider', to: 'auth#authorize', as: :auth
|
617
|
-
# ...
|
618
|
-
end
|
619
|
-
```
|
620
|
-
|
621
|
-
### User Management
|
622
|
-
|
623
|
-
Clavis creates a concern module that you can include in your User model:
|
624
|
-
|
625
|
-
```ruby
|
626
|
-
# app/models/user.rb
|
627
|
-
class User < ApplicationRecord
|
628
|
-
include Clavis::Models::Concerns::ClavisUserMethods
|
629
|
-
|
630
|
-
# Your existing user model code
|
631
|
-
end
|
632
|
-
```
|
633
|
-
|
634
|
-
This provides your User model with the `find_or_create_from_clavis` method that manages user creation from OAuth data.
|
635
|
-
|
636
|
-
### Session Management
|
637
|
-
|
638
|
-
Clavis handles user sessions through a concern module that is automatically included in your ApplicationController:
|
639
|
-
|
640
|
-
```ruby
|
641
|
-
# app/controllers/application_controller.rb
|
642
|
-
class ApplicationController < ActionController::Base
|
643
|
-
# Clavis automatically includes:
|
644
|
-
# include Clavis::Controllers::Concerns::Authentication
|
645
|
-
# include Clavis::Controllers::Concerns::SessionManagement
|
646
|
-
|
647
|
-
# Your existing controller code
|
648
|
-
end
|
649
|
-
```
|
650
|
-
|
651
|
-
#### Secure Cookie-Based Authentication
|
652
|
-
|
653
|
-
The SessionManagement concern uses a secure cookie-based approach that is compatible with Rails 8's authentication patterns:
|
654
|
-
|
655
|
-
- **Signed Cookies**: User IDs are stored in signed cookies with security settings like `httponly`, `same_site: :lax`, and `secure: true` (in production)
|
656
|
-
- **Security-First**: Cookies are configured with security best practices to protect against XSS, CSRF, and cookie theft
|
657
|
-
- **No Session Storage**: User authentication state is not stored in the session, avoiding session fixation attacks
|
658
|
-
|
659
|
-
#### Authentication Methods
|
660
|
-
|
661
|
-
The SessionManagement concern provides the following methods:
|
662
|
-
|
663
|
-
- `current_user` - Returns the currently authenticated user (if any)
|
664
|
-
- `authenticated?` - Returns whether a user is currently authenticated
|
665
|
-
- `sign_in_user(user)` - Signs in a user by setting a secure cookie
|
666
|
-
- `sign_out_user` - Signs out the current user by clearing cookies
|
667
|
-
- `store_location` - Stores the current URL to return to after authentication (uses session for this temporary data only)
|
668
|
-
- `after_login_path` - Returns the path to redirect to after successful login (stored location or root path)
|
669
|
-
- `after_logout_path` - Returns the path to redirect to after logout (login path or root path)
|
670
|
-
|
671
|
-
#### Compatibility with Existing Authentication
|
672
|
-
|
673
|
-
The system is designed to work with various authentication strategies:
|
674
|
-
|
675
|
-
1. **Devise**: If your application uses Devise, Clavis will automatically use Devise's `sign_in` and `sign_out` methods.
|
676
|
-
|
677
|
-
2. **Rails 8 Authentication**: Compatible with Rails 8's cookie-based authentication approach.
|
678
|
-
|
679
|
-
3. **Custom Cookie Usage**: If you're already using `cookies.signed[:user_id]`, Clavis will work with this approach.
|
680
|
-
|
681
|
-
#### Customizing Session Management
|
682
|
-
|
683
|
-
You can override any of these methods in your ApplicationController to customize the behavior:
|
684
|
-
|
685
|
-
```ruby
|
686
|
-
# app/controllers/application_controller.rb
|
687
|
-
class ApplicationController < ActionController::Base
|
688
|
-
# Override the default after_login_path
|
689
|
-
def after_login_path
|
690
|
-
dashboard_path # Redirect to dashboard instead of root
|
691
|
-
end
|
692
|
-
|
693
|
-
# Override sign_in_user to add additional behavior
|
694
|
-
def sign_in_user(user)
|
695
|
-
super # Call the original method
|
696
|
-
log_user_sign_in(user) # Add your custom behavior
|
697
|
-
end
|
698
|
-
|
699
|
-
# Use a different cookie name or format
|
700
|
-
def sign_in_user(user)
|
701
|
-
cookies.signed.permanent[:auth_token] = {
|
702
|
-
value: user.generate_auth_token,
|
703
|
-
httponly: true,
|
704
|
-
same_site: :lax,
|
705
|
-
secure: Rails.env.production?
|
706
|
-
}
|
707
|
-
end
|
708
|
-
|
709
|
-
# Customize how users are found
|
710
|
-
def find_user_by_cookie
|
711
|
-
return nil unless cookies.signed[:auth_token]
|
712
|
-
User.find_by_auth_token(cookies.signed[:auth_token])
|
713
|
-
end
|
714
|
-
end
|
715
|
-
```
|
716
|
-
|
717
|
-
## Configuration
|
718
|
-
|
719
|
-
See `config/initializers/clavis.rb` for all configuration options.
|
720
|
-
|
721
|
-
## Development
|
722
|
-
|
723
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
673
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
724
674
|
|
725
675
|
## Contributing
|
726
676
|
|
727
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
728
|
-
|
729
|
-
### Integration with has_secure_password
|
730
|
-
|
731
|
-
If your User model uses `has_secure_password` for authentication, you'll need to handle password validation carefully when creating users from OAuth. The generated ClavisUserMethods concern provides several strategies for dealing with this:
|
732
|
-
|
733
|
-
#### Option 1: Skip Password Validation (Recommended)
|
734
|
-
|
735
|
-
This approach adds a temporary attribute to mark OAuth users and skip password validation for them:
|
736
|
-
|
737
|
-
```ruby
|
738
|
-
# app/models/user.rb
|
739
|
-
class User < ApplicationRecord
|
740
|
-
include ClavisUserMethods
|
741
|
-
has_secure_password
|
742
|
-
|
743
|
-
# Skip password validation for OAuth users
|
744
|
-
validates :password, presence: true, length: { minimum: 8 },
|
745
|
-
unless: -> { skip_password_validation }, on: :create
|
746
|
-
end
|
747
|
-
```
|
748
|
-
|
749
|
-
The `skip_password_validation` attribute is set automatically in the OAuth flow.
|
750
|
-
|
751
|
-
#### Option 2: Set Random Password
|
752
|
-
|
753
|
-
Another approach is to set a random secure password for OAuth users:
|
754
|
-
|
755
|
-
```ruby
|
756
|
-
# app/models/user.rb
|
757
|
-
class User < ApplicationRecord
|
758
|
-
include ClavisUserMethods
|
759
|
-
has_secure_password
|
760
|
-
|
761
|
-
# Set a random password for OAuth users
|
762
|
-
before_validation :set_random_password,
|
763
|
-
if: -> { skip_password_validation && respond_to?(:password=) }
|
764
|
-
|
765
|
-
private
|
766
|
-
|
767
|
-
def set_random_password
|
768
|
-
self.password = SecureRandom.hex(16)
|
769
|
-
self.password_confirmation = password if respond_to?(:password_confirmation=)
|
770
|
-
end
|
771
|
-
end
|
772
|
-
```
|
773
|
-
|
774
|
-
#### Option 3: Bypass Validations (Use with Caution)
|
775
|
-
|
776
|
-
As a last resort, you can bypass validations entirely when creating OAuth users:
|
777
|
-
|
778
|
-
```ruby
|
779
|
-
# In app/models/concerns/clavis_user_methods.rb
|
780
|
-
def self.find_or_create_from_clavis(auth_hash)
|
781
|
-
# ... existing code ...
|
782
|
-
|
783
|
-
# Create a new user if none exists
|
784
|
-
if user.nil?
|
785
|
-
# ... set user attributes ...
|
786
|
-
|
787
|
-
# Bypass validations
|
788
|
-
user.save(validate: false)
|
789
|
-
end
|
790
|
-
|
791
|
-
# ... remainder of method ...
|
792
|
-
end
|
793
|
-
```
|
794
|
-
|
795
|
-
This approach isn't recommended as it might bypass important validations, but can be necessary in complex scenarios.
|
796
|
-
|
797
|
-
#### Database Setup
|
798
|
-
|
799
|
-
The Clavis generator automatically adds an `oauth_user` boolean field to your User model to help track which users were created through OAuth:
|
800
|
-
|
801
|
-
```ruby
|
802
|
-
# This is added automatically by the generator
|
803
|
-
add_column :users, :oauth_user, :boolean, default: false
|
804
|
-
```
|
805
|
-
|
806
|
-
This field is useful for conditional logic related to authentication methods.
|
807
|
-
|
808
|
-
### Session Management
|
809
|
-
|
810
|
-
```ruby
|
811
|
-
Clavis.configure do |config|
|
812
|
-
config.session_key = :clavis_current_user_id
|
813
|
-
config.user_finder_method = :find_or_create_from_clavis
|
814
|
-
end
|
815
|
-
```
|
816
|
-
|
817
|
-
### The OauthIdentity Model
|
818
|
-
|
819
|
-
Clavis stores OAuth credentials and user information in a polymorphic `OauthIdentity` model. This model has a `belongs_to :authenticatable, polymorphic: true` relationship, allowing it to be associated with any type of user model.
|
820
|
-
|
821
|
-
For convenience, the model also provides `user` and `user=` methods that are aliases for `authenticatable` and `authenticatable=`:
|
822
|
-
|
823
|
-
```ruby
|
824
|
-
# These are equivalent:
|
825
|
-
identity.user = current_user
|
826
|
-
identity.authenticatable = current_user
|
827
|
-
```
|
828
|
-
|
829
|
-
This allows you to use `identity.user` in your code even though the underlying database uses the `authenticatable` columns.
|
830
|
-
|
831
|
-
#### Key features of the OauthIdentity model:
|
832
|
-
|
833
|
-
- Secure token storage (tokens are automatically encrypted/decrypted)
|
834
|
-
- User information stored in the `auth_data` JSON column
|
835
|
-
- Automatic token refresh capabilities
|
836
|
-
- Unique index on `provider` and `uid` to prevent duplicate identities
|
837
|
-
|
838
|
-
### Webhook Providers
|
677
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/clayton/clavis.
|