Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add single-use tokenizer example to README #62

Merged
merged 1 commit into from
May 30, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,52 @@ end
config.passwordless_tokenizer = "::LuckyUserTokenizer"
```

#### Single-use tokenizer

With Rails 7.1 and [generates_token_for](https://api.rubyonrails.org/classes/ActiveRecord/TokenFor/ClassMethods.html#method-i-generates_token_for) you can create a single-use tokenizer. For example:

```ruby
class SingleUseTokenizer
def self.decode(token, resource_class, *args)
resource = resource_class.find_by_token_for(:passwordless_login, token)
raise Devise::Passwordless::ExpiredTokenError unless resource
raise Devise::Passwordless::InvalidTokenError unless resource.is_a?(resource_class)
[resource, {}]
end

def self.encode(resource, *args)
resource.generate_token_for(:passwordless_login)
end
end
```

Then in your `User` model:

```ruby
generates_token_for :passwordless_login, expires_in: passwordless_login_within do
current_sign_in_at
end
```

It relies on the `current_sign_in_at` attribute changing on a user after a successful login.
Once it changes, the same token will always be invalid and cannot be reused.

Since the same link cannot be visited twice, you may need to ignore `HEAD` requests
to avoid some email clients (ex: Outlook) visiting links with a `HEAD` request before
the `GET` request with something like:

```ruby
module Users
class PasswordlessMagicLinksController < Devise::MagicLinksController
def show
return render(plain: '') if request.method == 'HEAD'

super
end
end
end
```

## Multiple user (resource) types

Devise supports multiple resource types, so we do too.
Expand Down