0.0
Repository is archived
No commit activity in last 3 years
No release in over 3 years
trainmaster is a Rails engine that provides JWT-based session management platform for API development. This plugin is suitable for developing RESTful APIs that do not require an enterprise identity service. There are no cookies or non-unique IDs involved in this project.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

~> 3.1.7
~> 3.4.4
~> 1.5.4
~> 1.3.1
~> 2.0
~> 5.0.0
~> 0.3.0
~> 2.1.5
 Project Readme

trainmaster

Build Status Coverage Status Code Climate Gem Version

trainmaster is a Rails engine that provides a JWT-based session management platform for API development. This plugin is suitable for developing RESTful APIs that do not require an enterprise identity service. No cookies or non-unique IDs involved in this project.

It is a continuation of rails-identity which has been deprecated due to backwards compatibility issues.

This documentation uses httpie (rather than curl) to demonstrate making HTTP requests from the command line.

Features

  • Mountable Rails engine
  • JWT-based session management API (REST)
  • API key based authentication
  • Email verification workflow
  • Password reset workflow
  • Caching
  • OAuth authentication (beta)

Install

Install the gem, or go to your app's directory and add this line to your Gemfile:

gem 'trainmaster'

Then, add the following line in application.rb:

require 'trainmaster'

And the following in route.rb:

require 'trainmaster'

Rails.application.routes.draw do
  mount Trainmaster::Engine, at: "/"
end

Note that you may designate a different target prefix other than the root. Then, run bundle install and do rake routes to verify the routes.

Next, install migrations from trainmaster and perform migrations:

$ bundle exec rake trainmaster:install:migrations
$ bundle exec rake db:migrate RAILS_ENV=development

FYI, to see all rake tasks, do the following:

$ bundle exec rake --tasks

Other Plugins

trainmaster uses ActiveJob to perform tasks asynchronously, which requires a back-end module. For example, you can use DelayedJob by adding the following in Gemfile.

gem 'delayed_job_active_record'
gem 'daemons'

Also, email service must be specified in your app for sending out email verification token and password reset token. Note that the default email template is not sufficient for real use. You must define your own mailer action views to cater emails for your need.

To use OAuth, you must configure two endpoints. First, specify oauth_landing_page_url to the URL that will assign token (from query string) to a temporary storage such as cookie.

config.oauth_landing_page_url = '/oauth_success'

Once OAuth callback is successful, the controller will response a redirect (302) to the URL specified above with token=<actual token> as a query string.

Second, set a route for oauth failure.

get 'auth/failure', redirect_to('/oauth_failure')

This page should simply display failed authentication.

Other Changes

Trainmaster::User model is a STI model. It means your app can inherit from Trainmaster::User with additional attributes. All data will be stored in trainmaster_users table. This is particularly useful if you want to extend the model to meet your needs.

class User < Trainmaster::User
  # more validations, attributes, methods, ...
end

Running Your App

Now you're ready. Run the server to test:

$ bundle exec rails server

To allow DelayedJob tasks to run, do

$ RAILS_ENV=development bin/delayed_job start

Usage

Create User

Make a POST request on /users with email, password, and password_confirmation in the JSON payload.

$ http POST localhost:3000/users email=foo@example.com password="supersecret" password_confirmation="supersecret"

The response should be 201 if successful.

HTTP/1.1 201 Created
{
    "created_at": "2016-04-05T02:02:11.410Z",
    "deleted_at": null,
    "metadata": null,
    "role": 10,
    "updated_at": "2016-04-05T02:02:11.410Z",
    "username": "foo@example.com",
    "uuid": "68ddbb3a-fad2-11e5-8fc3-6c4008a6fa2a",
    "verified": false
}

This request will send an email verification token to the user's email. The app should craft the linked page to use the verification token to start a session and set verified to true by the following:

http PATCH localhost:3000/users/current verified=true token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOm51bGwsInNlc3Npb25fdXVpZCI6IjU5YTQwODRjLTAwNWMtMTFlNi1hN2ExLTZjNDAwOGE2ZmEyYSIsInJvbGUiOm51bGwsImlhdCI6MTQ2MDQzMDczMiwiZXhwIjoxNDYwNDM0MzMyfQ.rdi5JT5NzI9iuXjWfhXjYhc0xF-aoVAaAPWepgSUaH0

Note that current can be used when UUID is unknown but the token is specified. Also note that, if user's verified is false, some endpoints will reject the request.

Create Session

A proper way to create a session is to use username and password:

$ http POST localhost:3000/sessions username=foo@example.com password=supersecret

HTTP/1.1 201 Created
{
    "created_at": "2016-04-05T02:04:22.465Z",
    "metadata": null,
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOiI2OGRkYmIzYS1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJzZXNzaW9uX3V1aWQiOiJiNmZhZGJhNC1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJyb2xlIjoxMCwiaWF0IjoxNDU5ODIxODYyLCJleHAiOjE0NjEwMzE0NjJ9.B9Ld00JvHUZT37THrwFrHzUwxIx6s3UFPbVCCwYzRrQ",
    "updated_at": "2016-04-05T02:04:22.465Z",
    "user_uuid": "68ddbb3a-fad2-11e5-8fc3-6c4008a6fa2a",
    "uuid": "b6fadba4-fad2-11e5-8fc3-6c4008a6fa2a"
}

Notice this is essentially a login process for single-page apps. The client app should store the value of token in either localStore or cookie. (To allow cross-domain, you may want to use cookie.)

Delete Session

A session can be deleted via a DELETE method. This is essentially a logout process.

$ http DELETE localhost:3000/sessions/b6fadba4-fad2-11e5-8fc3-6c4008a6fa2a token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOiI2OGRkYmIzYS1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJzZXNzaW9uX3V1aWQiOiJiNmZhZGJhNC1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJyb2xlIjoxMCwiaWF0IjoxNDU5ODIxODYyLCJleHAiOjE0NjEwMzE0NjJ9.B9Ld00JvHUZT37THrwFrHzUwxIx6s3UFPbVCCwYzRrQ

HTTP/1.1 204 No Content

Make sure to remove the token from its storage. The old tokens will no longer work.

Password Reset

Since trainmaster is a RESTful service itself, password reset is done via a PATCH method on the user resource. But you must specify either the old password or a reset token. To use the old password:

$ http PATCH localhost:3000/users/68ddbb3a-fad2-11e5-8fc3-6c4008a6fa2a old_password="supersecret" password="reallysecret" password_confirmation="reallysecret" token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOiI2OGRkYmIzYS1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJzZXNzaW9uX3V1aWQiOiJiNmZhZGJhNC1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJyb2xlIjoxMCwiaWF0IjoxNDU5ODIxODYyLCJleHAiOjE0NjEwMzE0NjJ9.B9Ld00JvHUZT37THrwFrHzUwxIx6s3UFPbVCCwYzRrQ

To use a reset token, you must issue one first:

$ http PATCH localhost:3000/users/current username=foo@example.com issue_reset_token=true

HTTP/1.1 204 No Content

User token will be sent to the user's email. In a real application, the email would include a link to a page with JavaScript code automatically making a PATCH request to /users/current?token=<reset_token>.

Note that the response includes a JWT token that looks similar to a normal session token. Well a surprise! It is a session token but with a shorter life span (1 hour). So use it instead on the password reset request:

http PATCH localhost:3000/users/current password="reallysecret" password_confirmation="reallysecret" token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOiI2OGRkYmIzYS1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJzZXNzaW9uX3V1aWQiOiIzYjI5ZGI4OC1mYjlhLTExZTUtODNhOC02YzQwMDhhNmZhMmEiLCJyb2xlIjoxMCwiaWF0IjoxNDU5OTA3NTU0LCJleHAiOjE0NTk5MTExNTR9.g4iosqm8dOVUL5ErtCggsNAOs4WQV2u-heAUPf145jg

HTTP/1.1 200 OK
{
    "created_at": "2016-04-05T02:02:11.410Z",
    "deleted_at": null,
    "metadata": null,
    "reset_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOiI2OGRkYmIzYS1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJzZXNzaW9uX3V1aWQiOiIzYjI5ZGI4OC1mYjlhLTExZTUtODNhOC02YzQwMDhhNmZhMmEiLCJyb2xlIjoxMCwiaWF0IjoxNDU5OTA3NTU0LCJleHAiOjE0NTk5MTExNTR9.g4iosqm8dOVUL5ErtCggsNAOs4WQV2u-heAUPf145jg",
    "role": 10,
    "updated_at": "2016-04-06T01:55:45.163Z",
    "username": "foo@example.com",
    "uuid": "68ddbb3a-fad2-11e5-8fc3-6c4008a6fa2a",
    "verification_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOm51bGwsInNlc3Npb25fdXVpZCI6IjU5YTQwODRjLTAwNWMtMTFlNi1hN2ExLTZjNDAwOGE2ZmEyYSIsInJvbGUiOm51bGwsImlhdCI6MTQ2MDQzMDczMiwiZXhwIjoxNDYwNDM0MzMyfQ.rdi5JT5NzI9iuXjWfhXjYhc0xF-aoVAaAPWepgSUaH0",
    "verified": true
}

The token used with the request must match the reset token previously issued for the user.

Authentication and Authorization

There are two ways to do general authentication: token or API key.

To authorize a request to an action, use provided callbacks. trainmaster provides three controller callbacks for each approach:

  • Token
    • require_token
    • require_admin_token
    • accept_token - If a token is given, trainmaster will validate it.
  • API key
    • require_api_key
    • require_admin_api_key
    • accept_api_key - If an API key is given, trainmaster will validate it.
  • Both
    • require_auth - A token or an API key must be given.
    • require_admin_auth - A token or an API key of an admin must be given.
    • accept_auth - If either a token or an API key is given, trainmaster will validate it

To determine if the authenticated user has access to a specific resource object, use authorized?. An example of a resource authorization callback looks like the following:

def authorize_user_to_obj(obj)
  unless authorized?(obj)
    raise Repia::Errors::Unauthorized
  end
end

Other Notes

Instance Variables

ApplicationHelper module will define the following instance variables:

  • @auth_user - the authenticated user object
  • @auth_session - the authenticated session
  • @token - the token that authenticated the current session
  • @user - the context user, only available if get_user is called

Try not to overload these variables. You may use these variables to enforce further access control. Note that @auth_session and @token will be populated only if a token is used to authenticate.

Roles

For convenience, trainmaster pre-defined four roles:

  • Owner (1000) - the owner of the app
  • Admin (100) - the admin(s) of the app
  • User (10) - the user(s) of the app
  • Public (0) - the rest of the world