-
Notifications
You must be signed in to change notification settings - Fork 2
Example: Adding Remember Me option
Assuming you are already familiar with Adding authentication this guide will show how to implement a remember-me functionality, i.e.: persisting the user credentials even if the session is already over.
If you just wanna grab the code, check the warden-remember-me branch or see commit: 01f8fef.
Let's create a remember_me_token
field on the users
table:
# db/migrations/3_add_remember_me_token_to_users.rb
Sequel.migration do
change do
alter_table(:users) do
add_column :remember_me_token, String
add_index :remember_me_token
end
end
end
And create a method to regenerate the token for the user, when required:
# models/user.rb
require 'securerandom'
#... class User < ...
def remember_me!
token = SecureRandom.urlsafe_base64
until User.where(remember_me_token: token).empty? do
token = SecureRandom.urlsafe_base64
end
update remember_me_token: token
end
#...
Make sure that the remember_me
param is being passed:
diff --git a/views/user_sessions/new.html.erb b/views/user_sessions/new.html.erb
index 86574ca..65682e4 100644
--- a/views/user_sessions/new.html.erb
+++ b/views/user_sessions/new.html.erb
@@ -38,7 +38,7 @@
<input type="password" name="password" class="form-control" placeholder="Password" required>
<div class="checkbox">
<label>
- <input type="checkbox" value="remember-me"> Remember me
+ <input type="checkbox" name="remember_me"> Remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
Let's create a strategy for authentication via remember_me_token
:
# config/warden.rb
#...
Warden::Strategies.add(:remember_me_token) do
def valid?
request.cookies['remember_me_token']
end
def authenticate!
if token = request.cookies['remember_me_token']
if user = User.first(remember_me_token: token)
success!(user)
end
end
end
end
And also configure the middleware to use this strategy:
# ./yogurt.rb
use Warden::Manager do |manager|
manager.scope_defaults :default,
- strategies: [:password],
+ strategies: [:password, :remember_me_token],
action: 'user_sessions/unauthenticated'
manager.failure_app = self
end
If the user authenticated successfully we'll check if he sent the remember_me
param. If that's the case we'll generate a token and store on the cookies.
We don't have access to the response cookies on the context that we'll perform this check, so we'll have to pull in Rack::Cookies
.
Add gem 'rack-contrib'
to your Gemfile and bundle. Plug Rack::Cookies
to the application:
# ./yogurt.rb
require 'roda'
+require 'rack/contrib'
require './config/warden'
class Yogurt < Roda
+ use Rack::Cookies
use Rack::Session::Cookie, secret: ENV['SECRET']
use Rack::MethodOverride
Finally, we can hook onto the authentication succeeded and before logout events to setup and teardown the token:
# config/warden.rb
#...
Warden::Manager.after_authentication do |user, auth, opts|
if auth.params['remember_me']
user.remember_me!
auth.env['rack.cookies']['remember_me_token'] = user.remember_me_token
end
end
Warden::Manager.before_logout do |user, auth, opts|
user.update remember_me_token: nil
end
For some reason you'll notice you won't be able to log out, the issue is that before_logout
is not being triggered even though we called env['warden'].logout
. This is a documented issue.
This can be easily overcomed, by calling #authenticated?
before logging out:
diff --git a/routes/user_sessions.rb b/routes/user_sessions.rb
index aef5b56..046e229 100644
--- a/routes/user_sessions.rb
+++ b/routes/user_sessions.rb
@@ -10,7 +10,7 @@ class Yogurt
end
r.delete do
- env['warden'].logout
+ env['warden'].logout if env['warden'].authenticated?
flash[:success] = "You are logged out"
That's it.