• Stars
    star
    222
  • Rank 179,123 (Top 4 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 9 years ago
  • Updated over 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Secure Authentication for Single Page Applications

proof build status Gitter

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.