passkeys-rails 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: afbb635c3ddfd36d60bccbf936e2a67e091a6bb15822683c9c1db050cdd49909
4
- data.tar.gz: c9ef1cf6d9e9f5a760037efb831b856dbf706c0860fbf945eadd616c37cb297d
3
+ metadata.gz: a137b9b4f2ab7aac24fef0747ff112588215d496034f0542983df3790bc32de7
4
+ data.tar.gz: 412185131a984a883a11af7fae1bbbb69806819a978871a08e0929921c766fe4
5
5
  SHA512:
6
- metadata.gz: 92e7f2a72715c7c4b313d57f7dc1c928cf334375fcdd28fc104f86721562b45484220234e8e5924106fce344e94d95f9cb7e29d0d6879e4ac73451e7a1a17915
7
- data.tar.gz: 686d6a95cec8274eaadd3fa1555a89f0e1554bc248c1f30eca1ac253933a158e8d12e08bcd74e53b42cbe33a9e0a1630ed14d1ce3a7205a2fe289366d3cbe2c3
6
+ metadata.gz: bdf2974d31cb561bbeabaf599a29d99bb9cda2e4050cff913cbe5104a747c4eba2eb11a53936fcef013861ede39051208a2edbc3062a6b25a34476678388eba9
7
+ data.tar.gz: 149c795d0b56a7569170160426b466ff42abc75bcb2f46fc5adae3b35ad3eb9d6d465ce05f0834cbbfaf3bc30944d809b776c1112913c0e490175c316784e859
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ### 0.3.1
2
+
3
+ * Fixed a bug in reading session/cookie variables
4
+ * Added webauthn configuration parameters to this gem's configuration
5
+ * Moved configuration to its own class
6
+ * Added more info to the README
7
+
1
8
  ### 0.3.0
2
9
 
3
10
  * Added debug_register endpoint.
data/README.md CHANGED
@@ -1,42 +1,60 @@
1
+ # PasskeysRails - easy to integrate back end for implementing mobile passkeys
2
+
1
3
  [![Gem Version](https://badge.fury.io/rb/passkeys-rails.svg?cachebust=0.2.1)](https://badge.fury.io/rb/passkeys-rails)
2
4
  [![Build Status](https://app.travis-ci.com/alliedcode/passkeys-rails.svg?branch=main)](https://travis-ci.org/alliedcode/passkeys-rails)
3
5
  [![codecov](https://codecov.io/gh/alliedcode/passkeys-rails/branch/main/graph/badge.svg?token=UHSNJDUL21)](https://codecov.io/gh/alliedcode/passkeys-rails)
4
6
 
5
- # PasskeysRails
7
+ <p align="center" >
8
+ Created by <b>Troy Anderson, Allied Code</b> - <a href="https://alliedcode.com">alliedcode.com</a>
9
+ </p>
6
10
 
7
- Devise is awesome, but we don't need all that UI/UX for PassKeys, especially for an API back end.
11
+ PasskeysRails is a gem you can add to a Rails app to enable passskey registration and authorization from mobile front ends. PasskeysRails leverages webauthn for the cryptographic work, and presents a simple API interface for passkey registration, authentication, and testing.
8
12
 
9
13
  The purpose of this gem is to make it easy to provide a rails back end API that supports PassKey authentication. It uses the [`webauthn`](https://github.com/w3c/webauthn) gem to do the cryptographic work and presents a simple API interface for passkey registration and authentication.
10
14
 
11
15
  The target use case for this gem is a mobile application that uses a rails based API service to manage resources. The goal is to make it simple to register and authenticate users using passkeys from mobile applications in a rails API service.
12
16
 
17
+ What about [devise](https://github.com/heartcombo/devise)? Devise is awesome, but we don't need all that UI/UX for PassKeys, especially for an API back end.
18
+
19
+ ## Documentation
20
+ * [Usage](#usage)
21
+ * [Installation](#installation)
22
+ * [Rails Integration - Standard](#rails-Integration-standard)
23
+ * [Rails Integration - Grape](#rails-Integration-grape)
24
+ * [Notifications](#notifications)
25
+ * [Failure Codes](#failure-codes)
26
+ * [Testing](#testing)
27
+ * [Mobile App Integration](#mobile-application-integration)
28
+ * [Reference/Example Mobile Applications](#referenceexample-mobile-applications)
13
29
 
14
30
  ## Usage
15
31
 
16
- **PasskeysRails** maintains an `Agent` model and related `Passkeys`. If you have a user model, add `include PasskeysRails::Authenticatable` to your model and include the name of that class (e.g. `"User"`) in the `authenticatable_class` param when calling the register API or set the `PasskeysRails.default_class` to the name of that class.
32
+ **PasskeysRails** maintains a `PasskeysRails::Agent` model and related `PasskeysRails::Passkeys`. In rails apps that maintain their own "user" model, add `include PasskeysRails::Authenticatable` to that model and include the name of that class (e.g. `"User"`) in the `authenticatable_class` param when calling the register API or set the `PasskeysRails.default_class` to the name of that class.
33
+
34
+ In mobile apps, leverage the platform specific Passkeys APIs for ***registration*** and ***authentication***, and call the **PasskeysRails** API endpoints to complete the ceremony. **PasskeysRails** provides endpoints to support ***registration***, ***authentication***, ***token refresh***, and ***debugging***.
17
35
 
18
36
  ### Optionally providing a **"user"** model during registration
19
37
 
20
- **PasskeysRails** does not require that you supply your own model, but it's often useful to do so. For example, if you have a User model that you would like to have created at registration, you can supply the model name in the `finishRegistration` API call.
38
+ **PasskeysRails** does not require any application specific models, but it's often useful to have one. For example, a User model can be created at registration. **PasskeysRails** provides two mechanisms to support this. Either provide the name of the model in the `authenticatable_class` param when calling the `finishRegistration` endpoint, or set a `default_class` in `config/initializers/passkeys_rails.rb`.
21
39
 
22
- **PasskeysRails** supports multiple `"user"` models. Whatever model name you supply will be created during a successful the `finishRegiration` API call. When created, it will be provided an opportunity to do any initialization at that time.
40
+ **PasskeysRails** supports multiple different application specific models. Whatever model name supplied when calling the `finishRegistration` endpoint will be created during a successful the `finishRegiration` process. When created, it will be provided an opportunity to do any initialization at that time.
23
41
 
24
- There are two **PasskeysRails** configuration options related to this: `default_class` and `class_whitelist` - see below.
42
+ There are two **PasskeysRails** configuration options related to this: `default_class` and `class_whitelist`:
25
43
 
26
44
  #### `default_class`
27
45
 
28
- Configure `default_class` in `passkeys_rails.rb`. Its value will be used during registration if none is provided in the API call. The default value is `"User"`. Since the `default_class` is just a default, it can be overridden in the `finishRegiration` API call to use a different model. If no model is to be used by default, set it to nil.
46
+ Configure `default_class` in `config/initializers/passkeys_rails.rb`. Its value will be used during registration if none is provided in the API call. The default value is `"User"`. Since the `default_class` is just a default, it can be overridden in the `finishRegiration` API call to use a different model. If no model is to be used by default, set it to nil.
29
47
 
30
48
  #### `class_whitelist`
31
49
 
32
- Configure `class_whitelist` in `passkeys_rails.rb`. The default value is `nil`. When `nil`, no whitelist will be applied. If it is non-nil, it should be an array of class names that are allowed during registration. Supply an empty array to prevent **PasskeysRails** from attempting to create anything other than its own `PasskeysRails::Agent` during registration.
50
+ Configure `class_whitelist` in `config/initializers/passkeys_rails.rb`. The default value is `nil`. When `nil`, no whitelist will be applied. If it is non-nil, it should be an array of class names that are allowed during registration. Supply an empty array to prevent **PasskeysRails** from attempting to create anything other than its own `PasskeysRails::Agent` during registration.
33
51
 
34
52
  ## Installation
35
53
 
36
54
  Add this line to your application's Gemfile:
37
55
 
38
56
  ```ruby
39
- gem "passkeys_rails"
57
+ gem "passkeys-rails"
40
58
  ```
41
59
 
42
60
  And then execute:
@@ -58,29 +76,32 @@ $ rails generate passkeys_rails:install
58
76
 
59
77
  This will add the `config/initializers/passkeys_rails.rb` configuration file, passkeys routes, and a couple of database migrations to your project.
60
78
 
61
- ### Adding to an standard rails project
62
79
 
63
- 1. Add `before_action :authenticate_passkey!`
80
+ <a id="rails-Integration-standard"></a>
81
+ ## Rails Integration <p><small>Adding to a standard rails project</small></p>
64
82
 
65
- To prevent access to controller actions, add `before_action :authenticate_passkey!`. If an action is attempted without an authenticated entity, an error will be rendered in JSON with an :unauthorized result code.
83
+ - ### Add `before_action :authenticate_passkey!`
66
84
 
67
- 1. Use `current_agent` and `current_agent.authenticatable`
85
+ To prevent access to controller actions, add `before_action :authenticate_passkey!`. If an action is attempted without an authenticated entity, an error will be rendered in JSON with an :unauthorized result code.
68
86
 
69
- To access the currently authenticated entity, use `current_agent`. If you associated the registration of the agent with one of your own models, use `current_agent.authenticatable`. For example, if you associated the `User` class with the registration, `current_agent.authenticatable` will be a User object.
87
+ - ### Use `current_agent` and `current_agent.authenticatable`
70
88
 
71
- 1. Add `include PasskeysRails::Authenticatable` to model class(es)
89
+ To access the currently authenticated entity, use `current_agent`. If you associated the registration of the agent with one of your own models, use `current_agent.authenticatable`. For example, if you associated the `User` class with the registration, `current_agent.authenticatable` will be a User object.
90
+
91
+ - ### Add `include PasskeysRails::Authenticatable` to model class(es)
72
92
 
73
93
  If you have one or more classes that you want to use with authentication - e.g. a User class and an AdminUser class - add `include PasskeysRails::Authenticatable` to each of those classes. That adds a `registered?` method that you can call on your model to determine if they are registerd with your service, and a `registering_with(params)` method that you can override to initialize attributes of your model when it is created during registration. `params` is a hash with params passed to the API when registering. When called, your object has been built, but not yet saved. Upon return, **PasskeysRails** will attempt to save your object before finishing registration. If it is not valid, the registration will fail as well, returning the error error details to the caller.
74
94
 
75
- ### Adding to a Grape API rails project
95
+ <a id="rails-Integration-grape"></a>
96
+ ## Rails Integration - <p><small>Adding to a Grape API rails project</small></p>
76
97
 
77
- 1. Call `PasskeysRails.authenticate(request)` to authenticate the request.
98
+ - ### Call `PasskeysRails.authenticate(request)` to authenticate the request.
78
99
 
79
100
  Call `PasskeysRails.authenticate(request)` to get an object back that responds to `.success?` and `.failure?` as well as `.agent`, `.code`, and `.message`.
80
101
 
81
102
  Alternatively, call `PasskeysRails.authenticate!(request)` from a helper in your base class. It will raise a `PasskeysRails.Error` exception if the caller isn't authenticated. You can catch the exception and render an appropriate error. The exception contains the error code and message.
82
103
 
83
- 1. Consider adding the following helpers to your base API class:
104
+ - ### Consider adding the following helpers to your base API class:
84
105
 
85
106
  ```ruby
86
107
  helpers do
@@ -109,15 +130,17 @@ This will add the `config/initializers/passkeys_rails.rb` configuration file, pa
109
130
 
110
131
  To prevent access to various endpoints, add `before_action :authenticate_passkey!` or call `authenticate_passkey!` from any method that requires authentication. If an action is attempted without an authenticated entity, an error will be rendered in JSON with an :unauthorized result code.
111
132
 
112
- 1. Use `current_agent` and `current_agent.authenticatable`
133
+ - ### Use `current_agent` and `current_agent.authenticatable`
113
134
 
114
135
  To access the currently authenticated entity, use `current_agent`. If you associated the registration of the agent with one of your own models, use `current_agent.authenticatable`. For example, if you associated the `User` class with the registration, `current_agent.authenticatable` will be a User object.
115
136
 
116
- ### Notifications
137
+ ## Notifications
138
+
139
+ Certain actions trigger notifications that can be subscribed. See `subscribe` in `config/initializers/passkeys_rails.rb`.
117
140
 
118
- Certain actions trigger notifications that can be subscribed. See `subscribe` in `passkeys_rails.rb`.
141
+ These are completely optional. **PasskeysRails** will manage all the credentials and keys without these being implemented. They are useful for taking application specific actions like logging based on the authentication related events.
119
142
 
120
- #### Events
143
+ ### Events
121
144
 
122
145
  - `:did_register ` - a new agent has registered
123
146
 
@@ -125,7 +148,7 @@ Certain actions trigger notifications that can be subscribed. See `subscribe` i
125
148
 
126
149
  - `:did_refresh` - an agent's auth token has been refreshed
127
150
 
128
- A convenient place to set these up in is in `passkeys_rails.rb`
151
+ A convenient place to set these up in is in `config/initializers/passkeys_rails.rb`
129
152
 
130
153
  ```ruby
131
154
  PasskeysRails.config do |c|
@@ -147,10 +170,9 @@ PasskeysRails.subscribe(:did_register) do |event, agent, request|
147
170
  end
148
171
  ```
149
172
 
173
+ ## Failure Codes
150
174
 
151
- ### Authentication Failure
152
-
153
- 1. In the event of authentication failure, PasskeysRails returns an error code and message.
175
+ 1. In the event of authentication failure, **PasskeysRails** API endpoints render an error code and message.
154
176
 
155
177
  1. In a standard rails controller, the error code and message are rendered in JSON if `before_action :authenticate_passkey!` fails.
156
178
 
@@ -168,12 +190,10 @@ end
168
190
 
169
191
  In the future, the intention is to have the `.code` value stay consistent even if the `.message` changes. This also allows you to localize the messages as need using the code.
170
192
 
171
- ### Test Helpers
193
+ ## Testing
172
194
 
173
195
  PasskeysRails includes some test helpers for integration tests. In order to use them, you need to include the module in your test cases/specs.
174
196
 
175
- ### Integration tests
176
-
177
197
  Integration test helpers are available by including the `PasskeysRails::IntegrationHelpers` module.
178
198
 
179
199
  ```ruby
@@ -210,20 +230,38 @@ RSpec.describe 'Posts', type: :request do
210
230
  end
211
231
  ```
212
232
 
213
- ### Mobile Application Integration
233
+ ## Mobile Application Integration
234
+
235
+ ### Prerequisites
236
+
237
+ For iOS, you need to associate your app with your server. This amounts to setting up a special file on your server that defines the association. See [setup your apple-app-site-association](#Ensure-`.well-known/apple-app-site-association`-is-in-place)
238
+
214
239
 
215
- There are n groups of API endpoints that your mobile application may consume.
240
+ ### Mobile API Endpoints
241
+
242
+ There are 3 groups of API endpoints that your mobile application may consume.
216
243
 
217
244
  1. Unauthenticated (public) endpoints
218
245
  1. Authenticated (private) endpoints
219
246
  1. Passey endpoints (for supporting authentication)
220
247
 
221
- **Unauthenticated endpoints** can be consumed without and authentication.
248
+ **Unauthenticated endpoints** can be consumed without any authentication.
222
249
 
223
250
  **Authenticated endpoints** are protected by `authenticate_passkey!` or `PasskeysRails.authenticate!(request)`. Those methods check for and validate the `X-Auth` header, which must be set to the auth token returned in the `AuthResponse`, described below.
224
251
 
225
252
  **Passkey endpoints** are supplied by this gem and allow you to register a user, authenticate (login) a user, and refresh the token. This section describes these endpoints.
226
253
 
254
+ This gem supports the Passkey endpoints.
255
+
256
+ ### Endpoints
257
+
258
+ * [POST /passkeys/challenge](post-passkeys-challenge)
259
+ * [POST /passkeys/register](post-passkeys-register)
260
+ * [POST /passkeys/authenticate](post-passkeys-authenticate)
261
+ * [POST /passkeys/refresh](post-passkeys-refresh)
262
+ * [POST /passkeys/debug_register](post-passkeys-debug-register)
263
+ * [POST /passkeys/debug_login](post-passkeys-debug-login)
264
+
227
265
  All Passkey endpoints accept and respond with JSON.
228
266
 
229
267
  On **success**, they will respond with a 200 or 201 response code and relevant JSON.
@@ -249,7 +287,7 @@ Some endpoints return an `AuthResponse`, which has this JSON structure:
249
287
  }
250
288
  ```
251
289
 
252
- #### POST /passkeys/challenge
290
+ ### POST /passkeys/challenge
253
291
 
254
292
  Submit this to begin registration or authentication.
255
293
 
@@ -260,7 +298,7 @@ If the username is already in use, or anything else goes wrong, an error with co
260
298
  Omit the `username` when authenticating (logging in).
261
299
  The JSON response will be the `options_for_get` from webauthn.
262
300
 
263
- #### POST /passkeys/register
301
+ ### POST /passkeys/register
264
302
 
265
303
  After calling the `challenge` endpoint with a `username`, and handling its response, finish registering by calling this endpoint.
266
304
 
@@ -291,16 +329,16 @@ On **success**, the response is an `AuthResponse`.
291
329
 
292
330
  Possible **failure codes** (using the `ErrorResponse` structure) are:
293
331
 
294
- - webauthn_error - something is wrong with the credential
295
- - error - something else went wrong during credentail validation - see the `message` in the `ErrorResponse`
296
- - passkey_error - unable to persiste the passkey
297
- - invalid_authenticatable_class - the supplied authenticatable class can't be created/found (check spelling & capitalization)
298
- - invalid_class_whitelist - the whitelist in the passkeys_rails.rb configuration is invalid - be sure it's nil or an array
299
- - invalid_authenticatable_class - the supplied authenticatable class is not allowed - maybe it's not in the whitelist
300
- - record_invalid - the object of the supplied authenticatable class cannot be saved due to validation errors
301
- - agent_not_found - the agent referenced in the credential cannot be found in the database
332
+ - `webauthn_error` - something is wrong with the credential
333
+ - `error` - something else went wrong during credentail validation - see the `message` in the `ErrorResponse`
334
+ - `passkey_error` - unable to persist the passkey
335
+ - `invalid_authenticatable_class` - the supplied authenticatable class can't be created/found (check spelling & capitalization)
336
+ - `invalid_class_whitelist` - the whitelist in the passkeys_rails.rb configuration is invalid - be sure it's nil or an array
337
+ - `invalid_authenticatable_class` - the supplied authenticatable class is not allowed - maybe it's not in the whitelist
338
+ - `record_invalid` - the object of the supplied authenticatable class cannot be saved due to validation errors
339
+ - `agent_not_found` - the agent referenced in the credential cannot be found in the database
302
340
 
303
- #### POST /passkeys/authenticate
341
+ ### POST /passkeys/authenticate
304
342
 
305
343
  After calling the `challenge` endpoint without a `username`, and handling its response, finish authenticating by calling this endpoint.
306
344
 
@@ -325,12 +363,12 @@ On **success**, the response is an `AuthResponse`.
325
363
 
326
364
  Possible **failure codes** (using the `ErrorResponse` structure) are:
327
365
 
328
- - webauthn_error - something is wrong with the credential
329
- - passkey_not_found - the passkey referenced in the credential cannot be found in the database
366
+ - `webauthn_error` - something is wrong with the credential
367
+ - `passkey_not_found` - the passkey referenced in the credential cannot be found in the database
330
368
 
331
- #### POST /passkeys/refresh
369
+ ### POST /passkeys/refresh
332
370
 
333
- The token will expire after some time (configurable in passkeys_rails.rb). Before that happens, refresh it using this API. Once it's expired, to get a new token, use the /authentication API.
371
+ The token will expire after some time (configurable in `config/initializers/passkeys_rails.rb`). Before that happens, refresh it using this API. Once it expires, to get a new token, use the `/authentication` API.
334
372
 
335
373
  Supply the following JSON structure:
336
374
 
@@ -345,28 +383,28 @@ On **success**, the response is an `AuthResponse` with a new, refreshed token.
345
383
 
346
384
  Possible **failure codes** (using the `ErrorResponse` structure) are:
347
385
 
348
- - invalid_token - the token data is invalid
349
- - expired_token - the token is expired
350
- - token_error - some other error ocurred when decoding the token
386
+ - `invalid_token` - the token data is invalid
387
+ - `expired_token` - the token is expired
388
+ - `token_error` - some other error ocurred when decoding the token
351
389
 
352
- #### POST /passkeys/debug_login
390
+ ### POST /passkeys/debug_register
353
391
 
354
- As it may not be possible to acess Passkey functionality in mobile simulators, this endpoint may be called to login (authenticate) a username while bypassing the normal challenge/response sequence.
392
+ As it may not be possible to acess Passkey functionality in mobile simulators, this endpoint may be called to register a username while bypassing the normal challenge/response sequence.
355
393
 
356
- This endpoint only responds if DEBUG_LOGIN_REGEX is set in the server environment. It is very insecure to set this variable in a production environment as it bypasses all Passkey checks. It is only intended to be used during mobile application development.
394
+ This endpoint only responds if DEBUG_LOGIN_REGEX is set in the server environment. It is **very insecure to set this variable in a production environment** as it bypasses all Passkey checks. It is only intended to be used during mobile application development.
357
395
 
358
396
  To use this endpoint:
359
397
 
360
- 1. Manually create one or more PasskeysRails::Agent records in the database. A unique username is required for each.
361
-
362
398
  1. Set DEBUG_LOGIN_REGEX to a regex that matches any username you want to use during development - for example `^test(-\d+)?$` will match `test`, `test-1`, `test-123`, etc.
363
399
 
364
- 1. In the mobile application, call this endpoint in stead of the /passkeys/challenge and /passkeys/authenticate. The response is identicial to that of /passkeys/authenticate.
400
+ 1. In the mobile application, call this endpoint in stead of the /passkeys/challenge and /passkeys/register. The response is identicial to that of /passkeys/register.
365
401
 
366
- 1. Use the response as if it was from /passkeys/authenticate.
402
+ 1. Use the response as if it was from /passkeys/register.
367
403
 
368
404
  If you supply a username that doesn't match the DEBUG_LOGIN_REGEX, the endpoint will respond with an error.
369
405
 
406
+ Supply the following JSON structure:
407
+
370
408
  ```JSON
371
409
  # POST body
372
410
  {
@@ -377,71 +415,62 @@ On **success**, the response is an `AuthResponse`.
377
415
 
378
416
  Possible **failure codes** (using the `ErrorResponse` structure) are:
379
417
 
380
- - not_allowed - Invalid username (the username doesn't match the regex)
381
- - agent_not_found - No agent found with that username
382
-
383
- ## Reference/Example Mobile Applications
384
-
385
- **TODO**: Point to the soon-to-be-created reference mobile applications for how to use **passkeys-rails** for passkey authentication.
386
-
387
- ## Contributing
388
-
389
- ### Contributing Guidelines
418
+ - `not_allowed` - Invalid username (the username doesn't match the regex)
419
+ - `invalid_authenticatable_class` - the supplied authenticatable class can't be created/found (check spelling & capitalization)
420
+ - `invalid_class_whitelist` - the whitelist in the passkeys_rails.rb configuration is invalid - be sure it's nil or an array
421
+ - `invalid_authenticatable_class` - the supplied authenticatable class is not allowed - maybe it's not in the whitelist
422
+ - `record_invalid` - the object of the supplied authenticatable class cannot be saved due to validation errors
390
423
 
391
- Thank you for considering contributing to PasskeysRails! We welcome your help to improve and enhance this project. Whether it's a bug fix, documentation update, or a new feature, your contributions are valuable to the community.
392
-
393
- To ensure a smooth collaboration, please follow the guidelines below when submitting your contributions:
394
-
395
- #### Code of Conduct
396
-
397
- Please note that this project follows the [Code of Conduct](https://github.com/alliedcode/passkeys-rails/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. If you encounter any behavior that violates the code, please report it to the project maintainers.
398
-
399
- #### How to Contribute
400
-
401
- 1. Fork the repository on GitHub.
424
+ ### POST /passkeys/debug_login
402
425
 
403
- 2. Create a new branch for your contribution. Use a descriptive name that reflects the purpose of your changes.
404
-
405
- 3. Make your changes and commit them with clear and concise messages. Remember to follow the project's coding style and guidelines.
426
+ As it may not be possible to acess Passkey functionality in mobile simulators, this endpoint may be called to login (authenticate) a username while bypassing the normal challenge/response sequence.
406
427
 
407
- 4. Before submitting a pull request, ensure that your changes pass all existing tests and add relevant tests if applicable.
428
+ This endpoint only responds if DEBUG_LOGIN_REGEX is set in the server environment. It is **very insecure to set this variable in a production environment** as it bypasses all Passkey checks. It is only intended to be used during mobile application development.
408
429
 
409
- 5. Update the documentation if your changes introduce new features, modify existing behavior, or require user instructions.
430
+ To use this endpoint:
410
431
 
411
- 6. Squash your commits into a single logical commit if needed. Keep your commit history clean and focused.
432
+ 1. Manually create one or more PasskeysRails::Agent records in the database. A unique username is required for each.
412
433
 
413
- 7. Submit a pull request against the `main` branch of the original repository.
434
+ 1. Set DEBUG_LOGIN_REGEX to a regex that matches any username you want to use during development - for example `^test(-\d+)?$` will match `test`, `test-1`, `test-123`, etc.
414
435
 
415
- 8. Add a comment at the top of the CHANGELOG.md describing the change.
436
+ 1. In the mobile application, call this endpoint in stead of the /passkeys/challenge and /passkeys/authenticate. The response is identicial to that of /passkeys/authenticate.
416
437
 
417
- #### Pull Request Guidelines
438
+ 1. Use the response as if it was from /passkeys/authenticate.
418
439
 
419
- When submitting a pull request, please include the following details:
440
+ If you supply a username that doesn't match the DEBUG_LOGIN_REGEX, the endpoint will respond with an error.
420
441
 
421
- - A clear description of the changes you made and the problem it solves.
442
+ Supply the following JSON structure:
422
443
 
423
- - Any relevant issue numbers that your pull request addresses or fixes.
444
+ ```JSON
445
+ # POST body
446
+ {
447
+ "username": String
448
+ }
449
+ ```
450
+ On **success**, the response is an `AuthResponse`.
424
451
 
425
- - The steps to test your changes, so the project maintainers can verify them.
452
+ Possible **failure codes** (using the `ErrorResponse` structure) are:
426
453
 
427
- - Ensure that your pull request title and description are descriptive and informative.
454
+ - `not_allowed` - Invalid username (the username doesn't match the regex)
455
+ - `agent_not_found` - No agent found with that username
428
456
 
429
- #### Code Review Process
457
+ ## Reference/Example Mobile Applications
430
458
 
431
- All pull requests will undergo a code review process by the project maintainers. We appreciate your patience during this review process. Constructive feedback may be provided, and further changes might be requested.
459
+ There is a sample iOS app that integrates with **passkeys-rails** based server implementations. It's a great place to get a quick start on implementing passkyes in your iOS, iPadOS or MacOS apps.
432
460
 
433
- #### Contributor License Agreement
461
+ Check out the [PasskeysRailsDemo](https://github.com/alliedcode/PasskeysRailsDemo) app.
434
462
 
435
- By submitting a pull request, you acknowledge that your contributions will be licensed under the project's [MIT License](https://github.com/alliedcode/passkeys-rails/blob/main/MIT-LICENSE).
463
+ ## Contributing
436
464
 
437
- #### Reporting Issues
465
+ ### Contribution Guidelines
438
466
 
439
- If you encounter any bugs, problems, or have suggestions for improvement, please create an issue on the GitHub repository. Provide clear and detailed information about the issue to help us address it efficiently.
467
+ Thank you for considering contributing to PasskeysRails! We welcome your help to improve and enhance this project. Whether it's a bug fix, documentation update, or a new feature, your contributions are valuable to the community.
440
468
 
441
- #### Thank You
469
+ To ensure a smooth collaboration, please follow the [Contribution Guidelines](https://github.com/alliedcode/passkeys-rails/blob/main/CONTRIBUTION_GUIDELINES.md) when submitting your contributions.
442
470
 
443
- Your contributions are valuable, and we sincerely appreciate your efforts to improve PasskeysRails. Together, we can build a better software ecosystem for the community. Thank you for your support and happy contributing!
471
+ ### Code of Conduct
444
472
 
473
+ Please note that this project follows the [Code of Conduct](https://github.com/alliedcode/passkeys-rails/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. If you encounter any behavior that violates the code, please report it to the project maintainers.
445
474
 
446
475
  ## License
447
476
 
@@ -7,16 +7,17 @@ module PasskeysRails
7
7
  result = PasskeysRails::BeginChallenge.call!(username: challenge_params[:username])
8
8
 
9
9
  # Store the challenge so we can verify the future register or authentication request
10
- session[:passkeys_rails] = result.session_data
10
+ cookies[:passkeys_rails] = result.cookie_data
11
11
 
12
12
  render json: result.response.as_json
13
13
  end
14
14
 
15
15
  def register
16
+ cookie_data = cookies["passkeys_rails"] || {}
16
17
  result = PasskeysRails::FinishRegistration.call!(credential: attestation_credential_params.to_h,
17
18
  authenticatable_info: authenticatable_params&.to_h,
18
- username: session.dig(:passkeys_rails, :username),
19
- challenge: session.dig(:passkeys_rails, :challenge))
19
+ username: cookie_data["username"],
20
+ challenge: cookie_data["challenge"])
20
21
 
21
22
  broadcast(:did_register, agent: result.agent)
22
23
 
@@ -24,8 +25,9 @@ module PasskeysRails
24
25
  end
25
26
 
26
27
  def authenticate
28
+ cookie_data = cookies["passkeys_rails"] || {}
27
29
  result = PasskeysRails::FinishAuthentication.call!(credential: authentication_params.to_h,
28
- challenge: session.dig(:passkeys_rails, :challenge))
30
+ challenge: cookie_data["challenge"])
29
31
 
30
32
  broadcast(:did_authenticate, agent: result.agent)
31
33
 
@@ -10,7 +10,7 @@ module PasskeysRails
10
10
  options = result.options
11
11
 
12
12
  context.response = options
13
- context.session_data = session_data(options)
13
+ context.cookie_data = cookie_data(options)
14
14
  rescue Interactor::Failure => e
15
15
  context.fail! code: e.context.code, message: e.context.message
16
16
  end
@@ -25,7 +25,7 @@ module PasskeysRails
25
25
  end
26
26
  end
27
27
 
28
- def session_data(options)
28
+ def cookie_data(options)
29
29
  {
30
30
  username:,
31
31
  challenge: WebAuthn.standard_encoder.encode(options.challenge)
@@ -13,6 +13,8 @@ module PasskeysRails
13
13
  private
14
14
 
15
15
  def create_or_replace_unregistered_agent
16
+ context.fail! code: :origin_error, message: "config.wa_origin must be set" if WebAuthn.configuration.origin.blank?
17
+
16
18
  Agent.unregistered.where(username:).destroy_all
17
19
 
18
20
  agent = Agent.create(username:, webauthn_identifier: WebAuthn.generate_user_id)
@@ -28,7 +28,11 @@ module PasskeysRails
28
28
  rescue WebAuthn::Error => e
29
29
  context.fail!(code: :webauthn_error, message: e.message)
30
30
  rescue StandardError => e
31
- context.fail!(code: :error, message: e.message)
31
+ if e.message == "undefined method `end_with?' for nil:NilClass"
32
+ context.fail!(code: :webauthn_error, message: "origin is not set")
33
+ else
34
+ context.fail!(code: :error, message: e.message)
35
+ end
32
36
  end
33
37
 
34
38
  def store_passkey_and_register_agent!
@@ -51,7 +55,7 @@ module PasskeysRails
51
55
  def agent
52
56
  @agent ||= begin
53
57
  agent = Agent.find_by(username:)
54
- context.fail!(code: :agent_not_found, message: "Agent not found for session value: \"#{username}\"") if agent.blank?
58
+ context.fail!(code: :agent_not_found, message: "Agent not found for cookie value: \"#{username}\"") if agent.blank?
55
59
 
56
60
  agent
57
61
  end
@@ -60,4 +60,45 @@ PasskeysRails.config do |c|
60
60
  # c.subscribe(:did_register) do |event, agent, request|
61
61
  # puts("#{event} | #{agent.id} | #{request.headers}")
62
62
  # end
63
+
64
+ # PasskeysRails uses webauthn to help with the protocol.
65
+ # The following settings are passed throught webauthn.
66
+ # wa_origin is the only one requried
67
+
68
+ # This value needs to match `window.location.origin` evaluated by
69
+ # the User Agent during registration and authentication ceremonies.
70
+ # c.wa_origin = ENV['DEFAULT_HOST'] || https://myapp.mydomain.com
71
+
72
+ # Relying Party name for display purposes
73
+ # c.wa_relying_party_name = "My App Name"
74
+
75
+ # Optionally configure a client timeout hint, in milliseconds.
76
+ # This hint specifies how long the browser should wait for any
77
+ # interaction with the user.
78
+ # This hint may be overridden by the browser.
79
+ # https://www.w3.org/TR/webauthn/#dom-publickeycredentialcreationoptions-timeout
80
+ # c.wa_credential_options_timeout = 120_000
81
+
82
+ # You can optionally specify a different Relying Party ID
83
+ # (https://www.w3.org/TR/webauthn/#relying-party-identifier)
84
+ # if it differs from the default one.
85
+ #
86
+ # In this case the default would be "auth.example.com", but you can set it to
87
+ # the suffix "example.com"
88
+ #
89
+ # c.wa_rp_id = "example.com"
90
+
91
+ # Configure preferred binary-to-text encoding scheme. This should match the encoding scheme
92
+ # used in your client-side (user agent) code before sending the credential to the server.
93
+ # Supported values: `:base64url` (default), `:base64` or `false` to disable all encoding.
94
+ #
95
+ # c.wa_encoding = :base64url
96
+
97
+ # Possible values: "ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512", "RS1"
98
+ # Default: ["ES256", "PS256", "RS256"]
99
+ #
100
+ # c.wa_algorithms = ["ES256", "PS256", "RS256"]
101
+
102
+ # Append an algorithm to the existing set
103
+ # c.wa_algorithm = "PS512"
63
104
  end
@@ -1,49 +1,42 @@
1
1
  # rubocop:disable Naming/FileName
2
2
  require 'passkeys_rails/engine'
3
+ require 'passkeys_rails/configuration'
3
4
  require 'passkeys_rails/version'
4
5
  require_relative "generators/passkeys_rails/install_generator"
6
+ require 'forwardable'
5
7
 
6
8
  module PasskeysRails
7
9
  module Test
8
10
  autoload :IntegrationHelpers, 'passkeys_rails/test/integration_helpers'
9
11
  end
10
12
 
11
- # Secret used to encode the auth token.
12
- # Rails.application.secret_key_base is used if none is defined here.
13
- # Changing this value will invalidate all tokens that have been fetched
14
- # through the API.
15
- mattr_accessor(:auth_token_secret)
13
+ class << self
14
+ extend Forwardable
16
15
 
17
- # Algorithm used to generate the auth token.
18
- # Changing this value will invalidate all tokens that have been fetched
19
- # through the API.
20
- mattr_accessor :auth_token_algorithm, default: "HS256"
16
+ def_delegators :config, :auth_token_secret, :auth_token_algorithm, :auth_token_expires_in, :default_class, :class_whitelist
21
17
 
22
- # How long the auth token is valid before requiring a refresh or new login.
23
- # Set it to 0 for no expiration (not recommended in production).
24
- mattr_accessor :auth_token_expires_in, default: 30.days
18
+ def config
19
+ @config ||= begin
20
+ config = Configuration.new
21
+ yield(config) if block_given?
22
+ apply_webauthn_configuration(config)
25
23
 
26
- # Model to use when creating or authenticating a passkey.
27
- # This can be overridden when calling the API, but if no
28
- # value is supplied when calling the API, this value is used.
29
- # If nil, there is no default, and if none is supplied when
30
- # calling the API, no resource is created other than
31
- # a PaskeysRails::Agent that is used to track the passkey.
32
- #
33
- # This library doesn't assume that there will only be one
34
- # model, but it is a common use case, so setting the
35
- # default_class simplifies the use of the API in that case.
36
- mattr_accessor :default_class, default: "User"
24
+ config
25
+ end
26
+ end
27
+ end
37
28
 
38
- # By providing a class_whitelist, the API will require that
39
- # any supplied class is in the whitelist. If it is not, the
40
- # auth API will return an error. This prevents a caller from
41
- # attempting to create an unintended record on registration.
42
- # If nil, any model will be allowed.
43
- # If [], no model will be allowed.
44
- # This should be an array of symbols or strings,
45
- # for example: %w[User AdminUser]
46
- mattr_accessor :class_whitelist, default: nil
29
+ def self.apply_webauthn_configuration(config)
30
+ WebAuthn.configure do |c|
31
+ c.origin = config.wa_origin
32
+ c.rp_name = config.wa_relying_party_name if config.wa_relying_party_name
33
+ c.credential_options_timeout = config.wa_credential_options_timeout if config.wa_credential_options_timeout
34
+ c.rp_id = config.wa_rp_id if config.wa_rp_id
35
+ c.encoding = config.wa_encoding if config.wa_encoding
36
+ c.algorithms = config.wa_algorithms if config.wa_algorithms
37
+ c.algorithms << config.wa_algorithm if config.wa_algorithm
38
+ end
39
+ end
47
40
 
48
41
  # Convenience method to subscribe to various events in PasskeysRails.
49
42
  #
@@ -104,16 +97,6 @@ module PasskeysRails
104
97
  message: auth.message)
105
98
  end
106
99
 
107
- class << self
108
- def config
109
- yield self
110
- end
111
- end
112
-
113
100
  require 'passkeys_rails/railtie' if defined?(Rails)
114
101
  end
115
-
116
- ActiveSupport.on_load(:before_initialize) do
117
- PasskeysRails.auth_token_secret ||= Rails.application.secret_key_base
118
- end
119
102
  # rubocop:enable Naming/FileName
@@ -0,0 +1,62 @@
1
+ module PasskeysRails
2
+ class Configuration
3
+ # Secret used to encode the auth token.
4
+ # Rails.application.secret_key_base is used if none is defined here.
5
+ # Changing this value will invalidate all tokens that have been fetched
6
+ # through the API.
7
+ attr_accessor :auth_token_secret
8
+
9
+ # Algorithm used to generate the auth token.
10
+ # Changing this value will invalidate all tokens that have been fetched
11
+ # through the API.
12
+ attr_accessor :auth_token_algorithm
13
+
14
+ # How long the auth token is valid before requiring a refresh or new login.
15
+ # Set it to 0 for no expiration (not recommended in production).
16
+ attr_accessor :auth_token_expires_in
17
+
18
+ # Model to use when creating or authenticating a passkey.
19
+ # This can be overridden when calling the API, but if no
20
+ # value is supplied when calling the API, this value is used.
21
+ # If nil, there is no default, and if none is supplied when
22
+ # calling the API, no resource is created other than
23
+ # a PaskeysRails::Agent that is used to track the passkey.
24
+ #
25
+ # This library doesn't assume that there will only be one
26
+ # model, but it is a common use case, so setting the
27
+ # default_class simplifies the use of the API in that case.
28
+ attr_accessor :default_class
29
+
30
+ # By providing a class_whitelist, the API will require that
31
+ # any supplied class is in the whitelist. If it is not, the
32
+ # auth API will return an error. This prevents a caller from
33
+ # attempting to create an unintended record on registration.
34
+ # If nil, any model will be allowed.
35
+ # If [], no model will be allowed.
36
+ # This should be an array of symbols or strings,
37
+ # for example: %w[User AdminUser]
38
+ attr_accessor :class_whitelist
39
+
40
+ # webauthn settings
41
+ attr_accessor :wa_origin,
42
+ :wa_relying_party_name,
43
+ :wa_credential_options_timeout,
44
+ :wa_rp_id,
45
+ :wa_encoding,
46
+ :wa_algorithms,
47
+ :wa_algorithm
48
+
49
+ def initialize
50
+ # defaults
51
+ @auth_token_secret = Rails.application.secret_key_base
52
+ @auth_token_algorithm = "HS256"
53
+ @auth_token_expires_in = 30.days
54
+ @default_class = "User"
55
+ @wa_origin = "https://example.com"
56
+ end
57
+
58
+ def subscribe(event_name)
59
+ PasskeysRails.subscribe(event_name)
60
+ end
61
+ end
62
+ end
@@ -1,3 +1,3 @@
1
1
  module PasskeysRails
2
- VERSION = "0.3.0".freeze
2
+ VERSION = "0.3.1".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: passkeys-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Troy Anderson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-01 00:00:00.000000000 Z
11
+ date: 2023-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -380,6 +380,7 @@ files:
380
380
  - lib/generators/passkeys_rails/templates/README
381
381
  - lib/generators/passkeys_rails/templates/passkeys_rails_config.rb
382
382
  - lib/passkeys-rails.rb
383
+ - lib/passkeys_rails/configuration.rb
383
384
  - lib/passkeys_rails/engine.rb
384
385
  - lib/passkeys_rails/railtie.rb
385
386
  - lib/passkeys_rails/test/integration_helpers.rb