Skip to content

Instantly share code, notes, and snippets.

@dnlserrano
Last active January 19, 2021 09:56
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save dnlserrano/24c975d63e721a02b883 to your computer and use it in GitHub Desktop.
Save dnlserrano/24c975d63e721a02b883 to your computer and use it in GitHub Desktop.
Custom Authentication Controllers
class ApiController < ApplicationController
# define which model will act as token authenticatable
acts_as_token_authentication_handler_for Login
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :null_session
respond_to :json
skip_before_filter :verify_authenticity_token, if: :json_request?
private
def json_request?
request.format.json?
end
end
MyApp::Application.routes.draw do
root :to => 'welcome#index'
devise_for :logins, :skip => [:sessions, :registrations, :passwords]
devise_scope :login do
post 'login' => 'sessions#create', :as => :login
delete 'logout' => 'sessions#destroy', :as => :logout
post 'register' => 'registrations#create', :as => :registers
delete 'delete_account' => 'registrations#destroy', :as => :delete_account
end
get 'greetings', to: 'greetings#index'
end
### Signup (on signup, the user is automatically logged in):
```
curl -X POST -H "Accept: application/json" --data 'user[username]=dnlserrano&user[email]=dnlserrano@mail.com&user[password]=password&user[password_confirmation]=password' localhost:3000/register > ~/Desktop/new.html
```
### Logout (authenticates using token):
```
curl -X DELETE -H "Accept: application/json" --data 'user_email=dnlserrano@mail.com&user_token=<token>' localhost:3000/logout > ~/Desktop/new.html
```
### Login (initiates session with valid email/password combination):
```
curl -X POST -H "Accept: application/json" --data 'user[email]=dnlserrano@mail.com&user[password]=password' localhost:3000/login > ~/Desktop/new.html
```
### Greetings (needs to authenticate):
```
curl -X GET -H "Accept: application/json" --data 'user_email=dnlserrano@mail.com&user_token=<token>' localhost:3000/greetings > ~/Desktop/new.html
```
### Delete account:
```
curl -X DELETE -H "Accept: application/json" --data 'user_email=dnlserrano@mail.com&user_token=<token>' localhost:3000/delete_account > ~/Desktop/new.html
```
class GreetingsController < ApiController
def index
end
end
class CustomFailure < Devise::FailureApp
def redirect_url
root_path
end
def respond
if http_auth?
http_auth
else
if request.format.json?
self.status = :unauthorized
self.response_body = { :sucess => false, :data => { :message => "login failed" }}.to_json
self.content_type = "json"
else
redirect
end
end
end
end
class Login < ActiveRecord::Base
# token authenticatable using simple_token_authentication gem
acts_as_token_authenticatable
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :user, dependent: :destroy
validates :email, :presence => true, :uniqueness => true, :email => true
end
class User < ActiveRecord::Base
validates_uniqueness_of :username
validates_presence_of :username
validates_length_of :username, :minimum => 1 , :maximum => 20, :allow_nil => false
validates_presence_of :first_name
validates_length_of :last_name, :maximum => 60, :allow_nil => false
validates_presence_of :last_name
validates_length_of :last_name, :maximum => 60, :allow_nil => false
belongs_to :login
end
class RegistrationsController < Devise::RegistrationsController
respond_to :json
skip_before_filter :verify_authenticity_token, if: :json_request?
acts_as_token_authentication_handler_for Login
skip_before_filter :authenticate_entity_from_token!, only: [ :create ]
skip_before_filter :authenticate_entity!, only: [ :create ]
skip_before_filter :authenticate_scope!
append_before_filter :authenticate_scope!, only: [ :destroy ]
def create
ActiveRecord::Base.transaction do
begin
build_resource(login_params)
if resource.save
resource.create_user!(user_params)
@success = true
else
clean_up_passwords resource
@success = false
@errors = resource.errors
raise t('.invalid_signup')
end
rescue Exception => e
@success = false
@errors = e.message if @errors.blank? and not e.message.nil?
raise ActiveRecord::Rollback
end
end
end
def destroy
resource.destroy
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
end
private
def login_params
permitted_params.extract!(:email, :password, :password_confirmation).permit!
end
def user_params
permitted_params.extract!(:username, :first_name, :last_name, :bday).permit!
end
def permitted_params
params.require(:login).permit(:email, :password, :password_confirmation, :username, :first_name, :last_name, :bday)
end
def json_request?
request.format.json?
end
end
class SessionsController < Devise::SessionsController
respond_to :json
skip_before_filter :verify_authenticity_token, if: :json_request?
acts_as_token_authentication_handler_for Login
skip_before_filter :authenticate_entity_from_token!
skip_before_filter :authenticate_entity!
before_filter :authenticate_entity_from_token!, :only => [:destroy]
before_filter :authenticate_entity!, :only => [:destroy]
def create
warden.authenticate!(:scope => resource_name)
@login = current_login
end
def destroy
if login_signed_in?
@login = current_login
@login.authentication_token = nil
@login.save
else
render 'failure'
end
end
private
def json_request?
request.format.json?
end
end
FactoryGirl.define do
factory :login do
email 'user@test.com'
password 'password'
end
end
FactoryGirl.define do
factory :user do
login
username 'username'
first_name 'First'
last_name 'Last'
end
end
require 'spec_helper'
require 'json'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.include RegistrationHelpers, :type => :controller
end
describe RegistrationsController do
before(:each) do
@request.env["devise.mapping"] = Devise.mappings[:login]
@request.env["HTTP_ACCEPT"] = "application/json"
@request.env["CONTENT_TYPE"] = "application/json"
end
it "creates an account" do
req = dummy_registration
post "create", req
content = req[:login]
login = Login.first
expect(login).not_to be_nil
expect(login.email).to eq(content[:email])
expect(login.password).to eq(login.password_confirmation)
expect(login.authentication_token).not_to be_nil
expect(login.created_at).to be < Time.now
user = login.user
expect(user).not_to be_nil
expect(user.username).to eq(content[:username])
expect(user.first_name).to eq(content[:first_name])
expect(user.last_name).to eq(content[:last_name])
expect(user.bday).to be_nil
expect(user.created_at).to be < Time.now
end
it "does not create a duplicate account" do
post "create", dummy_registration
post "create", dummy_registration
dummy_email = dummy_registration[:login][:email]
users = User.all
expect(users.count).to eq(1)
logins = Login.all
expect(logins.count).to eq(1)
user = users.first
login = logins.first
expect(user.login).to eq(login)
end
it "validates uniqueness of email" do
first_req = dummy_registration
second_req = dummy_registration
second_req[:login][:username] = "second_req_name"
post "create", first_req
post "create", second_req
users = User.all
expect(users.count).to eq(1)
logins = Login.all
expect(logins.count).to eq(1)
user = users.first
login = logins.first
expect(user.login).to eq(login)
end
it "validates uniqueness of username" do
first_req = dummy_registration
second_req = dummy_registration
second_req[:login][:email] = "second_email@test.com"
post "create", first_req
post "create", second_req
users = User.all
expect(users.count).to eq(1)
logins = Login.all
expect(logins.count).to eq(1)
user = users.first
login = logins.first
expect(user.login).to eq(login)
end
it "validates email format" do
wrong_req = dummy_registration
wrong_req[:login][:email] = "wrong_email"
post "create", wrong_req
wrong_req = dummy_registration
wrong_req[:login][:email] = "wrong_email@"
post "create", wrong_req
wrong_req = dummy_registration
wrong_req[:login][:email] = "wrong_email@a."
post "create", wrong_req
logins = Login.all
expect(logins.count).to be_zero
users = User.all
expect(users.count).to be_zero
end
it "validates presence of email" do
wrong_req = dummy_registration
wrong_req[:login][:email] = nil
post "create", wrong_req
logins = Login.all
expect(logins.count).to be_zero
users = User.all
expect(users.count).to be_zero
end
it "validates presence of first name" do
wrong_req = dummy_registration
wrong_req[:login][:first_name] = nil
post "create", wrong_req
logins = Login.all
expect(logins.count).to be_zero
users = User.all
expect(users.count).to be_zero
end
it "validates presence of last name" do
wrong_req = dummy_registration
wrong_req[:login][:last_name] = nil
post "create", wrong_req
logins = Login.all
expect(logins.count).to be_zero
users = User.all
expect(users.count).to be_zero
end
it "fails to destroy account for wrong user credentials" do
dummy_user = create(:user)
login = dummy_user.login
params = {
:login_email => "invalid@email.com",
:login_token => "1NV4LidT0k3N"
}
delete "destroy", params
logins = Login.all
expect(logins.count).to eq(1)
users = User.all
expect(users.count).to eq(1)
end
it "destroys existent account for correct user credentials" do
dummy_user = create(:user)
login = dummy_user.login
login_email = login.email
login_token = login.authentication_token
params = {
:login_email => login_email,
:login_token => login_token
}
delete "destroy", params
logins = Login.all
expect(logins.count).to be_zero
users = User.all
expect(users.count).to be_zero
end
end
module RegistrationHelpers
def dummy_registration
dummy = {
:login => {
:email => 'user@test.com',
:password => 'password',
:password_confirmation => 'password',
:username => 'test',
:first_name => 'Daniel',
:last_name => 'Serrano'
}
}
return dummy
end
end
json.success true
json.data do
json.greeting 'Hello! You were able to authenticate, congrats!'
end
json.success @success
json.data do
if @success
json.message t('.message')
else
json.errors @errors if not @errors.blank?
end
end
json.success true
json.data do
json.message t('.message')
end
json.success true
json.data do
json.auth_token @login.authentication_token
json.message t('.message')
end
json.success true
json.data do
json.message t('.message')
end
json.sucess false
json.data do
json.message t('.message')
end
@aihaddad
Copy link

aihaddad commented Nov 8, 2014

hi, @dnlserrano thanks for he gist and all the discussions going on. I am in a similar situation with this, except that mu user has a polymorphic belongs_to association with 4 roles. I've already created the user model to work with devise, and I was wondering if this example could be done without the Login model. This is my first API and I'd really appreciate your help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment