Devise Part 10: Devise Token Auth
Published: Mar 16, 2022
Last updated: Mar 16, 2022
This post will explore how we can use devise_token_auth
to setup opaque tokens with our applications.
This is a great solution for SPAs or mobile apps that require authentication.
Each request will refresh the token and expires them in a short time. It also maintains sessions for each client which enables us to have multi-session capabilities for the same user on their different devices.
Part 10 will naturally move onto part 11 which will be around setting up simple authentication tokens with Doorkeeper to enable you to share a public API for your users.
Final source code can be found here.
Prerequisites
- Basic familiarity with setting up a new Rails project.
Getting started
We will use Rails to initialize the project devise-token-auth-example
:
# Create a new rails project $ rails new devise-token-auth-example $ cd devise-token-auth-example
We need to add a number of gems for us to use devise_token_auth
.
gem 'devise' gem 'devise_token_auth', '>= 1.2.0', git: "https://github.com/lynndylanhurley/devise_token_auth" gem 'omniauth'
The latest version on Git plays well with Rails 7, but at the time of writing a new version of the gem is yet to be released.
In the terminal, we now need to install these gems as well as generate the install helpers given from devise
and devise_token_auth
.
# Install gems $ bundle # Generate and setup files $ bin/rails g devise:install $ bin/rails g devise_token_auth:install User auth # Create and migrate DBs $ bin/rails db:create db:migrate
At this point, we have created a new User
model and set it so the /auth
endpoints will be where we authenticate.
Updating our application controller
As I will be using Postman
for demonstration purposes, we will update our settings to skip forgery.
Update app/controllers/application_controller.rb
to the following:
class ApplicationController < ActionController::Base skip_forgery_protection include DeviseTokenAuth::Concerns::SetUserByToken end
Creating a new controller to test against
We will be requiring a user authentication for the /api/v1/home
route, so let's create a controller to demonstrate this.
# Generate controller $ bin/rails g controller api/v1/home
Update app/controllers/api/v1/home_controller.rb
:
class Api::V1::HomeController < ApplicationController before_action :authenticate_user! def index render json: { message: 'Welcome to the API' } end end
Updating our routes
We need to update our config/routes.rb
file to include our home controller as well as setting up devise_token_auth
.
Update the file to the following:
Rails.application.routes.draw do mount_devise_token_auth_for 'User', at: 'auth' # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Defines the root path route ("/") # root "articles#index" namespace :api do namespace :v1 do resources :home, only: [:index] end end end
Setting up the mount_devise_token_auth_for
method will provide us with a number of useful endpoint.
If you look through bin/rails routes
, you'll find the following related to the /auth
endpoints:
new_user_session GET /auth/sign_in(.:format) devise_token_auth/sessions#new user_session POST /auth/sign_in(.:format) devise_token_auth/sessions#create destroy_user_session DELETE /auth/sign_out(.:format) devise_token_auth/sessions#destroy new_user_password GET /auth/password/new(.:format) devise_token_auth/passwords#new edit_user_password GET /auth/password/edit(.:format) devise_token_auth/passwords#edit user_password PATCH /auth/password(.:format) devise_token_auth/passwords#update PUT /auth/password(.:format) devise_token_auth/passwords#update POST /auth/password(.:format) devise_token_auth/passwords#create cancel_user_registration GET /auth/cancel(.:format) devise_token_auth/registrations#cancel new_user_registration GET /auth/sign_up(.:format) devise_token_auth/registrations#new edit_user_registration GET /auth/edit(.:format) devise_token_auth/registrations#edit user_registration PATCH /auth(.:format) devise_token_auth/registrations#update PUT /auth(.:format) devise_token_auth/registrations#update DELETE /auth(.:format) devise_token_auth/registrations#destroy POST /auth(.:format) devise_token_auth/registrations#create auth_validate_token GET /auth/validate_token(.:format) devise_token_auth/token_validations#validate_token auth_failure GET /auth/failure(.:format) devise_token_auth/omniauth_callbacks#omniauth_failure GET /auth/:provider/callback(.:format) devise_token_auth/omniauth_callbacks
We can use the POST /auth/sign_in
route to authenticate our user in our clients, as well as a POST request to /auth
to sign up new users.
Signing our can be done DELETE /auth/sign_out
.
Testing our authentication with Postman
We can sign up a new user by create a POST request to /auth
with email
, password
and password_confirmation
fields:
Signing up
From the response, we need to grab three of the headers for our authentication.
client
uid
access-token
Response headers
If we make a request to our /api/v1/home
route without providing the required headers, you will receive an unauthorized response.
No token provided
We can remedy this by putting in all three required headers:
Providing the token
After using the token in a request, a new one will be given back and you need to track it between requests.
New token returned after a request
After usage of a token, it will expire soon-after.
Expired token being used
Amazing! We have gone through a workflow of what accessing protected endpoints will look like.
Summary
Today's post demonstrated how to authenticate a user using devise_token_auth
. This method makes use of opaque tokens and can be highly secure in how it works.
In the next part, we will extend this application by using Doorkeeper to create a v2 of the API that will be for public usage.
Resources and further reading
Photo credit: tods
Devise Part 10: Devise Token Auth
Introduction