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.
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,224 @@ 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
+ ## User Management
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
+ 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.
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
+ The concern provides:
201
+ - Helper methods for accessing OAuth data
202
+ - Logic to create or find users based on OAuth data
203
+ - Support for skipping password validation for OAuth users
152
204
 
153
- ## Database Setup
205
+ ### The OauthIdentity Model
154
206
 
155
- The generator creates migrations for:
207
+ 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.
156
208
 
157
- 1. OAuth identities table
158
- 2. User model OAuth fields
209
+ For convenience, the model also provides `user` and `user=` methods that are aliases for `authenticatable` and `authenticatable=`:
210
+
211
+ ```ruby
212
+ # These are equivalent:
213
+ identity.user = current_user
214
+ identity.authenticatable = current_user
215
+ ```
159
216
 
160
- ## Integrating with Existing Authentication
217
+ This allows you to use `identity.user` in your code even though the underlying database uses the `authenticatable` columns.
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
+ #### Key features of the OauthIdentity model:
220
+
221
+ - Secure token storage (tokens are automatically encrypted/decrypted)
222
+ - User information stored in the `auth_data` JSON column
223
+ - Automatic token refresh capabilities
224
+ - Unique index on `provider` and `uid` to prevent duplicate identities
225
+
226
+ ### Integration with has_secure_password
174
227
 
175
- ## Controller Integration
228
+ 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:
229
+
230
+ #### Option 1: Skip Password Validation (Recommended)
176
231
 
177
- Include the authentication concern:
232
+ This approach adds a temporary attribute to mark OAuth users and skip password validation for them:
178
233
 
179
234
  ```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
235
+ # app/models/user.rb
236
+ class User < ApplicationRecord
237
+ include ClavisUserMethods
238
+ has_secure_password
239
+
240
+ # Skip password validation for OAuth users
241
+ validates :password, presence: true, length: { minimum: 8 },
242
+ unless: -> { skip_password_validation }, on: :create
202
243
  end
203
244
  ```
204
245
 
205
- ## User Model Integration
246
+ The `skip_password_validation` attribute is set automatically in the OAuth flow.
206
247
 
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:
248
+ #### Option 2: Set Random Password
208
249
 
209
- ```bash
210
- # Generate the Clavis user methods concern
211
- rails generate clavis:user_method
212
- ```
250
+ Another approach is to set a random secure password for OAuth users:
213
251
 
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
252
+ ```ruby
253
+ # app/models/user.rb
254
+ class User < ApplicationRecord
255
+ include ClavisUserMethods
256
+ has_secure_password
217
257
 
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)
258
+ # Set a random password for OAuth users
259
+ before_validation :set_random_password,
260
+ if: -> { skip_password_validation && respond_to?(:password=) }
261
+
262
+ private
263
+
264
+ def set_random_password
265
+ self.password = SecureRandom.hex(16)
266
+ self.password_confirmation = password if respond_to?(:password_confirmation=)
267
+ end
268
+ end
269
+ ```
222
270
 
223
- ### Customizing User Creation
271
+ #### Option 3: Bypass Validations (Use with Caution)
224
272
 
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:
273
+ As a last resort, you can bypass validations entirely when creating OAuth users:
226
274
 
227
275
  ```ruby
228
276
  # 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...
277
+ def self.find_or_create_from_clavis(auth_hash)
278
+ # ... existing code ...
246
279
 
247
- # Create new user if none exists
280
+ # Create a new user if none exists
248
281
  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
- )
282
+ # ... set user attributes ...
256
283
 
257
- user.save!
284
+ # Bypass validations
285
+ user.save(validate: false)
258
286
  end
259
287
 
260
- # Create or update the OAuth identity...
288
+ # ... remainder of method ...
261
289
  end
262
290
  ```
263
291
 
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.
292
+ This approach isn't recommended as it might bypass important validations, but can be necessary in complex scenarios.
273
293
 
274
- ⚠️ **IMPORTANT**: You **MUST** customize this method to set all required fields for your User model!
294
+ #### Database Setup
275
295
 
276
- We use `with_indifferent_access` to reliably access fields regardless of whether keys are strings or symbols. The auth_hash typically contains:
296
+ The Clavis generator automatically adds an `oauth_user` boolean field to your User model to help track which users were created through OAuth:
277
297
 
278
298
  ```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)
299
+ # This is added automatically by the generator
300
+ add_column :users, :oauth_user, :boolean, default: false
292
301
  ```
293
302
 
294
- Example of customized user creation:
295
-
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
- )
308
- ```
303
+ This field is useful for conditional logic related to authentication methods.
309
304
 
310
- ### Helper Methods
305
+ ### Session Management
311
306
 
312
- The concern includes the `OauthAuthenticatable` module, which provides helper methods:
307
+ Clavis handles user sessions through a concern module that is automatically included in your ApplicationController:
313
308
 
314
309
  ```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
322
- ```
310
+ # Available in your controllers after installation:
311
+ # include Clavis::Controllers::Concerns::Authentication
312
+ # include Clavis::Controllers::Concerns::SessionManagement
323
313
 
324
- ### Handling Password Requirements
325
-
326
- For password-protected User models, the concern includes a commented-out conditional validation:
314
+ # Current user helper method
315
+ def current_user
316
+ @current_user ||= cookies.signed[:user_id] && User.find_by(id: cookies.signed[:user_id])
317
+ end
327
318
 
328
- ```ruby
329
- # Uncomment in app/models/concerns/clavis_user_methods.rb
330
- validates :password, presence: true, unless: :oauth_user?
319
+ # Sign in helper
320
+ def sign_in_user(user)
321
+ cookies.signed[:user_id] = {
322
+ value: user.id,
323
+ httponly: true,
324
+ same_site: :lax,
325
+ secure: Rails.env.production?
326
+ }
327
+ end
331
328
  ```
332
329
 
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
337
-
338
- ### Using a Different Class or Method
330
+ #### Authentication Methods
339
331
 
340
- You can configure Clavis to use a different class or method name:
332
+ The SessionManagement concern provides:
341
333
 
342
- ```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
350
- end
351
- ```
334
+ - `current_user` - Returns the currently authenticated user
335
+ - `authenticated?` - Returns whether a user is authenticated
336
+ - `sign_in_user(user)` - Signs in a user by setting a secure cookie
337
+ - `sign_out_user` - Signs out the current user
338
+ - `store_location` - Stores URL to return to after authentication
339
+ - `after_login_path` - Path to redirect to after login
340
+ - `after_logout_path` - Path to redirect to after logout
352
341
 
353
342
  ## View Integration
354
343
 
355
- Include view helpers:
344
+ Include view helpers in your application:
356
345
 
357
346
  ```ruby
358
- # app/helpers/oauth_helper.rb
359
- module OauthHelper
347
+ # app/helpers/application_helper.rb
348
+ module ApplicationHelper
360
349
  include Clavis::ViewHelpers
361
350
  end
362
351
  ```
363
352
 
364
- ### Importing Stylesheets
353
+ ### Using OAuth Buttons
365
354
 
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
384
-
385
- Use in views:
355
+ Basic button usage:
386
356
 
387
357
  ```erb
388
358
  <div class="oauth-buttons">
389
359
  <%= clavis_oauth_button :google %>
390
360
  <%= clavis_oauth_button :github %>
361
+ <%= clavis_oauth_button :microsoft %>
362
+ <%= clavis_oauth_button :facebook %>
363
+ <%= clavis_oauth_button :apple %>
391
364
  </div>
392
365
  ```
393
366
 
394
- Customize buttons:
367
+ Customizing buttons:
395
368
 
396
369
  ```erb
370
+ <!-- Custom text -->
397
371
  <%= clavis_oauth_button :google, text: "Continue with Google" %>
372
+
373
+ <!-- Custom CSS class -->
398
374
  <%= clavis_oauth_button :github, class: "my-custom-button" %>
375
+
376
+ <!-- Additional HTML attributes -->
377
+ <%= clavis_oauth_button :apple, html: { data: { turbo: false } } %>
378
+
379
+ <!-- All customization options -->
380
+ <%= clavis_oauth_button :github,
381
+ text: "Sign in via GitHub",
382
+ class: "custom-button github-button",
383
+ icon_class: "custom-icon",
384
+ html: { id: "github-login" } %>
399
385
  ```
400
386
 
401
- ## Routes Configuration
387
+ The buttons come with built-in styles and brand-appropriate icons for the supported providers.
402
388
 
403
- The generator mounts the engine:
389
+ ## Advanced Features
390
+
391
+ ### Testing Your Integration
392
+
393
+ Access standardized user info:
404
394
 
405
395
  ```ruby
406
- # config/routes.rb
407
- mount Clavis::Engine => "/auth"
396
+ # From most recent OAuth provider
397
+ current_user.oauth_email
398
+ current_user.oauth_name
399
+ current_user.oauth_avatar_url
400
+
401
+ # From specific provider
402
+ current_user.oauth_email("google")
403
+ current_user.oauth_name("github")
404
+
405
+ # Check if OAuth user
406
+ current_user.oauth_user?
408
407
  ```
409
408
 
410
- ## Token Refresh
409
+ ### Token Refresh
411
410
 
412
411
  Provider support:
413
412
 
@@ -426,7 +425,7 @@ provider = Clavis.provider(:google, redirect_uri: "https://your-app.com/auth/goo
426
425
  new_tokens = provider.refresh_token(oauth_identity.refresh_token)
427
426
  ```
428
427
 
429
- ## Custom Providers
428
+ ### Custom Providers
430
429
 
431
430
  Use the Generic provider:
432
431
 
@@ -466,22 +465,30 @@ end
466
465
  Clavis.register_provider(:example_oauth, ExampleOAuth)
467
466
  ```
468
467
 
469
- ## Provider-Specific Setup
468
+ ## Provider Setup
470
469
 
471
- Callback URI format for all providers:
470
+ ### Setting Up OAuth Redirect URIs in Provider Consoles
472
471
 
473
- ```
474
- https://your-domain.com/auth/:provider/callback
475
- ```
472
+ When setting up OAuth, correctly configuring redirect URIs in both your app and the provider's developer console is crucial:
473
+
474
+ #### Google
475
+ 1. Go to [Google Cloud Console](https://console.cloud.google.com)
476
+ 2. Navigate to "APIs & Services" > "Credentials"
477
+ 3. Create or edit an OAuth 2.0 Client ID
478
+ 4. Under "Authorized redirect URIs" add exactly the same URI as in your Clavis config:
479
+ - For development: `http://localhost:3000/auth/google/callback`
480
+ - For production: `https://your-app.com/auth/google/callback`
476
481
 
477
- Setup guides for:
478
- - [Google](#google)
479
- - [GitHub](#github)
480
- - [Apple](#apple)
481
- - [Facebook](#facebook)
482
- - [Microsoft](#microsoft)
482
+ #### GitHub
483
+ 1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
484
+ 2. Navigate to "OAuth Apps" and create or edit your app
485
+ 3. In the "Authorization callback URL" field, add exactly the same URI as in your Clavis config
486
+
487
+ #### Common Errors
488
+ - **Error 400: redirect_uri_mismatch** - This means the URI in your code doesn't match what's registered in the provider's console
489
+ - **Solution**: Ensure both URIs match exactly, including protocol (http/https), domain, port, and full path
483
490
 
484
- ## Rate Limiting
491
+ ## Security & Rate Limiting
485
492
 
486
493
  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
494
 
@@ -552,287 +559,14 @@ ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |name, start,
552
559
  end
553
560
  ```
554
561
 
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
562
  ## Development
574
563
 
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.
564
+ 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
565
 
577
566
  The `rails-app` directory contains a Rails application used for integration testing and is not included in the gem package.
578
567
 
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.
568
+ To install this gem onto your local machine, run `bundle exec rake install`.
724
569
 
725
570
  ## Contributing
726
571
 
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
572
+ Bug reports and pull requests are welcome on GitHub at https://github.com/clayton/clavis.