Authentication for Modern Web Apps
Proof is JWT Authentication with Rails Made Easy. It is designed for web applications which must authenticate users with Rails APIs. It is:
- Based on JWT, a secure, open authentication standard.
- Easy to Use and Modular. It works out of the box with Devise, and can easily be customized to work with any database setup.
- Designed for Single-Page Applications and API-Driven experiences.
How it Works
When your web application send a POST request to the login
action with an identifier (email, username, etc.) and password, proof authenticates the user and creates a JWT token. This token is encrypted and signed using a secret key generated by your rails application, so a hacker who gets a hold of it can't modify it. The token identifies the user it will authenticate for and an expiration time. Your web application stores the token and sends it as an HTTP header with every request. Proof will take care of authenticating the token, verifying it is not expired, and retrieving the current User on every request, so you just need to add a before_action
call for the actions you want to restrict to authenticated users.
Installation
In your Gemfile:
gem 'proof-rails', require: "proof"
Usage
To quickly start using proof with Devise, use the generator:
rails generate proof:devise User
Where User
is the class name of your User model.
For other authentication solutions or custom Devise setups, you must first use proof_actions in the controller you would like the login route to be on:
class AuthenticationController < ActionController::Base
proof_actions
end
proof_actions
will create a login
action on the controller it is called in. This login action will accept the parameters identifier
and password
. Proof will use these parameters to authenticate the corresponding user and create a JWT token. If the parameters correspond to a nonexistant or invalid user, proof will return an error message with the status 401 Unauthorized
.
Using proof_actions
proof_actions
accepts a number of options, allowing you to configure it for any authentication setup:
proof_actions authenticatable: :User, identifier: :email, password: :password, authenticate: :authenticate
authenticatable:
specifies the name of the model which represents your users.
identifier:
specifies the name of the field which uniquely identifies each user, and which the user uses to log in. For devise, this is the email
field.
password:
specifies the name of the param which holds the password used to authenticate a user. This is almost always password
, but sometimes the param goes by a different name.
authenticate:
specifies the name of the method on the User model which accepts a password to authenticate the user. This is the name of an instance method on the User model which accepts the password
param, and returns either true or false to indicate authentication.
Handle Bad Tokens
By default proof will handle invalid token by rendering json like so:
{ error: "Invalid Credentials." }
To customize this you have to options you can pass to proof_actions
.
error_json:
which allows you to fully overide the default json. Used like so:
proof_actions authenticatable: :User, authenticate: :valid_password?, error_json: { errors: { general: [ "Not Authorized" ] } } do |user, token|
Api::V1::SessionSerializer.new(user).attributes
end
raise_error:
which instead of rendering json will trigger an exception that can be handled like so:
rescue_from Proof::NotAuthorizedError, with: :user_not_authorized
This will likely need to be in the ApplicationController.
The usage looks like:
Used like so:
proof_actions authenticatable: :User, authenticate: :valid_password?, raise_error: true do |user, token|
Api::V1::SessionSerializer.new(user).attributes
end
Modify Token
Proof also allows for a optional block that returns a hash to modify the json return
proof_actions authenticatable: :User do |user, token|
{
user_id: user.id,
email: user.email,
auth_token: token
}
end
How to handle the token
When your application sends a POST
request to the login
action, it will return JSON with the key auth_token
if it finds a valid user. Your application must then save this token and send it with every request under the Authorization
HTTP header, in the Bearer format: Bearer [token]
.
Routeing
You must route the login
action yourself. For example, if you had a controller named AuthenticationController
, you could create a /login
route like so:
post '/login', to: 'authentication#login'
In order to restrict an action to authenticated users, simply use a before_action
call for the actions you'd like to restrict:
before_action :require_proof, only: [:index, :show]
require_proof
accepts the option authenticatable
, representing the name of the User class. This is User
by default. If you'd like to change it, you must call before_action
like so:
before_action only: [:index, :show] do
require_proof authenticatable: :User
end
Where :User
is the name of the model class representing your users.
After Authorization
Proof gives you access to the following helper methods in the views and the controllers:
current_user
Returns the current authenticated user in the controller and the views. For example:
before_action :require_proof, only: :index
def index
@posts = current_user.posts
end
Token
If you need to access the token yourself you can use Proof::Token
.
For example if you want to send a token in a Serializer you can create the token like so.
Proof::Token.from_data({ user_id: object.id }, false, secret, 'HS256', expiration_date)
The parameters look like this:
- data
- expire_token = true
- secret_key = Rails.application.secrets.secret_key_base
- algorithm = 'HS256'
- expiration_date = 24.hours.from_now.to_i
To be used correctly data
always needs a valid user_id
.
Contribute
Proof is fully-tested using MiniTest. Make sure to write tests for new functionality you add in, and run rake test
before pushing your changes.
To help out, either contribute to this repository, or write adapters for popular client-side libraries, such as Ember, Angular, and React.