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.
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 just three steps:
21
+ Get up and running with OAuth authentication in these simple steps:
22
+
23
+ ### Step 1: Installation
14
24
 
15
25
  ```ruby
16
- # 1. Add to your Gemfile and run bundle install
26
+ # Add to your Gemfile and run bundle install
17
27
  gem 'clavis'
18
28
  ```
19
29
 
20
30
  ```bash
21
- # 2. Run the installation generator
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
- # 3. Configure a provider (in config/initializers/clavis.rb)
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
- Then add an OAuth login button to your view:
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. [Installation](#installation)
65
- 3. [Basic Configuration](#basic-configuration)
66
- 4. [Database Setup](#database-setup)
67
- 5. [Controller Integration](#controller-integration)
68
- 6. [User Model Integration](#user-model-integration)
69
- 7. [View Integration](#view-integration)
70
- 8. [Routes Configuration](#routes-configuration)
71
- 9. [Session Management](#session-management)
72
- 10. [Integration with has_secure_password](#integration-with-has_secure_password)
73
- 11. [Token Refresh](#token-refresh)
74
- 12. [Custom Providers](#custom-providers)
75
- 13. [Provider-Specific Setup](#provider-specific-setup)
76
- 14. [Rate Limiting](#rate-limiting)
77
- 15. [Testing Your Integration](#testing-your-integration)
78
- 16. [Troubleshooting](#troubleshooting)
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', '~> 0.6.2'
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
- ## Basic Configuration
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
- ## Setting Up OAuth Redirect URIs in Provider Consoles
192
+ ### Configuration Options
133
193
 
134
- When setting up OAuth, correctly configuring redirect URIs in both your app and the provider's developer console is crucial:
194
+ See `config/initializers/clavis.rb` for all configuration options.
135
195
 
136
- ### Google
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
- ### GitHub
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
- ### Common Errors
150
- - **Error 400: redirect_uri_mismatch** - This means the URI in your code doesn't match what's registered in the provider's console
151
- - **Solution**: Ensure both URIs match exactly, including protocol (http/https), domain, port, and full path
200
+ ```ruby
201
+ Clavis.configure do |config|
202
+ # Enable detailed authentication flow logs
203
+ config.verbose_logging = true
204
+ end
205
+ ```
152
206
 
153
- ## Database Setup
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
- The generator creates migrations for:
213
+ This is particularly useful for debugging OAuth integration issues, but should typically be disabled in production.
156
214
 
157
- 1. OAuth identities table
158
- 2. User model OAuth fields
215
+ ## User Management
159
216
 
160
- ## Integrating with Existing Authentication
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
- 1. Configure as shown above
163
- 2. Run the generator
164
- 3. Include the module in your User model:
165
- ```ruby
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
- ## Controller Integration
224
+ ### The OauthIdentity Model
176
225
 
177
- Include the authentication concern:
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
- # app/controllers/auth_controller.rb
181
- class AuthController < ApplicationController
182
- include Clavis::Controllers::Concerns::Authentication
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
- ## User Model Integration
236
+ This allows you to use `identity.user` in your code even though the underlying database uses the `authenticatable` columns.
206
237
 
207
- Clavis delegates user creation and management to your application through a finder method. After installing Clavis, you need to set up your User model to handle OAuth users:
238
+ #### Key features of the OauthIdentity model:
208
239
 
209
- ```bash
210
- # Generate the Clavis user methods concern
211
- rails generate clavis:user_method
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
- This generates:
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
- ### Customizing User Creation
249
+ #### Option 1: Skip Password Validation (Recommended)
224
250
 
225
- The generated concern includes a method to find or create users from OAuth data. By default, it only sets the email field, which may not be sufficient for your User model:
251
+ This approach adds a temporary attribute to mark OAuth users and skip password validation for them:
226
252
 
227
253
  ```ruby
228
- # In app/models/concerns/clavis_user_methods.rb
229
- def find_or_create_from_clavis(auth_hash)
230
- # For OpenID Connect providers (like Google), we use the sub claim as the stable identifier
231
- # For other providers, we use the uid
232
- identity = if auth_hash[:id_token_claims]&.dig(:sub)
233
- Clavis::OauthIdentity.find_by(
234
- provider: auth_hash[:provider],
235
- uid: auth_hash[:id_token_claims][:sub]
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
- ### OpenID Connect Providers and Stable Identifiers
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
- ⚠️ **IMPORTANT**: You **MUST** customize this method to set all required fields for your User model!
267
+ #### Option 2: Set Random Password
275
268
 
276
- We use `with_indifferent_access` to reliably access fields regardless of whether keys are strings or symbols. The auth_hash typically contains:
269
+ Another approach is to set a random secure password for OAuth users:
277
270
 
278
271
  ```ruby
279
- # Access these fields with info.dig(:field_name)
280
- info = auth_hash[:info].with_indifferent_access
281
-
282
- # Common fields available in info:
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
- ```ruby
297
- # Convert to HashWithIndifferentAccess for reliable key access
298
- info = auth_hash[:info].with_indifferent_access if auth_hash[:info]
299
-
300
- user = new(
301
- email: info&.dig(:email),
302
- first_name: info&.dig(:given_name) || info&.dig(:first_name),
303
- last_name: info&.dig(:family_name) || info&.dig(:last_name),
304
- username: info&.dig(:nickname) || "user_#{SecureRandom.hex(4)}",
305
- avatar_url: info&.dig(:picture) || info&.dig(:image),
306
- terms_accepted: true
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
- ### Helper Methods
290
+ #### Option 3: Bypass Validations (Use with Caution)
311
291
 
312
- The concern includes the `OauthAuthenticatable` module, which provides helper methods:
292
+ As a last resort, you can bypass validations entirely when creating OAuth users:
313
293
 
314
294
  ```ruby
315
- # Available on any user instance
316
- user.oauth_user? # => true if the user has any OAuth identities
317
- user.oauth_identity # => the primary OAuth identity
318
- user.oauth_avatar_url # => the profile picture URL
319
- user.oauth_name # => the name from OAuth
320
- user.oauth_email # => the email from OAuth
321
- user.oauth_token # => the access token
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
- ### Handling Password Requirements
311
+ This approach isn't recommended as it might bypass important validations, but can be necessary in complex scenarios.
325
312
 
326
- For password-protected User models, the concern includes a commented-out conditional validation:
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
- # Uncomment in app/models/concerns/clavis_user_methods.rb
330
- validates :password, presence: true, unless: :oauth_user?
318
+ # This is added automatically by the generator
319
+ add_column :users, :oauth_user, :boolean, default: false
331
320
  ```
332
321
 
333
- This allows you to:
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
- ### Using a Different Class or Method
324
+ ### Session Management
339
325
 
340
- You can configure Clavis to use a different class or method name:
326
+ Clavis handles user sessions through a concern module that is automatically included in your ApplicationController:
341
327
 
342
328
  ```ruby
343
- # config/initializers/clavis.rb
344
- Clavis.configure do |config|
345
- # Use a different class
346
- config.user_class = "Account"
347
-
348
- # Use a different method name
349
- config.user_finder_method = :create_from_oauth
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/oauth_helper.rb
359
- module OauthHelper
366
+ # app/helpers/application_helper.rb
367
+ module ApplicationHelper
360
368
  include Clavis::ViewHelpers
361
369
  end
362
370
  ```
363
371
 
364
- ### Importing Stylesheets
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
- Use in views:
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
- Customize buttons:
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
- ## Routes Configuration
406
+ The buttons come with built-in styles and brand-appropriate icons for the supported providers.
402
407
 
403
- The generator mounts the engine:
408
+ ## Advanced Features
409
+
410
+ ### Testing Your Integration
411
+
412
+ Access standardized user info:
404
413
 
405
414
  ```ruby
406
- # config/routes.rb
407
- mount Clavis::Engine => "/auth"
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
- ## Token Refresh
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
- ## Custom Providers
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-Specific Setup
487
+ ## Provider Setup
488
+
489
+ ### Setting Up OAuth Redirect URIs in Provider Consoles
470
490
 
471
- Callback URI format for all providers:
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
- https://your-domain.com/auth/:provider/callback
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
- Setup guides for:
478
- - [Google](#google)
479
- - [GitHub](#github)
480
- - [Apple](#apple)
481
- - [Facebook](#facebook)
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 spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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/your-username/clavis.
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.