mardi 7 juillet 2015

Exchanging user/password for token in a Rails API

I'm having a little trouble figuring out how to create a token based authentication system for a Rails API.

I'm planning to build an Ember app where users should be able to sign in by providing a password and username.

On the rails side I have setup a an AuthenticationToken model which checks the authorization token and non-persisted secret. I store an encrypted secret based on the secret and the AuthenticationToken id (a BSON::ObjectId).

Basically my idea is that client should be able to authenticate as long as it hangs onto the secret. And then it needs to request a new access token. I want to keep it stateless by avoiding sessions.

I'm very confused about what the route and controller should look like where the user trades his user/pass for an access token. As the numerous blog post/tutorial I have found omits that part. Do I just create a simple POST route where the client sends credentials and gets the token back as JSON? Or should I use HTTP BASIC AUTH?

class User
  include Mongoid::Document
  include ActiveModel::SecurePassword
  embeds_many :tokens, class_name: 'Users::AuthenticationToken'
  field :email, type: String
  field :username, type: String
  field :password_digest, type: String
  validates :email, presence: true, uniqueness: true
  has_secure_password
end


require "securerandom"
require "bcrypt"

class Users::AuthenticationToken
  include Mongoid::Document
  embedded_in :user

  attr_accessor :secret
  field :hashed_secret

  validates :hashed_secret, uniqueness: true, presence: true
  before_validation :generate_secret

  def self.find_by_id(id)
    begin
      id = BSON::ObjectId.from_string(id) unless id.is_a?(BSON::ObjectId)
      tokens = User.find_by('tokens._id' => id).try(:tokens)
      tokens ? tokens.find_by(id: id) : nil
    rescue Mongoid::Errors::DocumentNotFound, BSON::ObjectId::Invalid
      nil
    end
  end

  # @return [Boolean]
  def has_secret? secret
    BCrypt::Password.new(hashed_secret) == secret
  end

  def authenticate!(secret)
    user if has_secret? secret
  end

  private

  def generate_secret
    self.secret = SecureRandom.urlsafe_base64(32)
    self.hashed_secret = BCrypt::Password.create secret, cost: cost
  end

  def cost
    Rails.env.test? ? 1 : 10
  end
end

I also have a authenticate method which looks something like this:

def authenticate!
  token = Users::AuthenticationToken.find_by_id(token_id)
  user = token.try(:authenticate!, secret)
  user.nil? ? fail!("Could not log in") : success!(user)
end

Aucun commentaire:

Enregistrer un commentaire