Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flesh out server-side setup instructions in README #155

Closed
wants to merge 3 commits into from
Closed
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 94 additions & 43 deletions packages/ember-simple-auth-devise/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,100 @@ This is an extension to the Ember.SimpleAuth library that provides an
authenticator and an authorizer that are compatible with customized
installations of [Devise](https:/plataformatec/devise).

## Server-side setup

As token authentication is not actually part of Devise anymore, there are some
customizations necessary on the server side. Most of this walk-through is
adapted from [José Valim's gist on token authentication]
(https://gist.github.com/josevalim/fb706b1e933ef01e4fb6).

First, you must add a colunm to your database in which to store the
authentication token:

```ruby
class AddAuthenticationTokenToUser < ActiveRecord::Migration
def change
add_column :users, :authentication_token, :string
end
end
```

Then, you must set up your model to generate the token:

```ruby
class User < ActiveRecord::Base
...
before_save :ensure_authentication_token

def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end

private

def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.where(authentication_token: token).first
end
end
end
```

When a user signs in using Ember, requests are made with a JSON API.
Unfortunately, Devise does not give the responses we need in JSON, so we must
make our own controller:

```ruby
class SessionsController < Devise::SessionsController
def create
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
yield resource if block_given?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's only necessary in Devise's implementation of the #create method - you'd never get a block here.

response = {
auth_token: resource.authentication_token,
auth_email: resource.email
}
render json: response, status: 201
end
end
```

Then, we need to tell Devise to use this controller instead of its default:

```ruby
MyRailsApp::Application.routes.draw do
devise_for :users, controllers: {sessions: "sessions"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that the default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you override a Devise controller, you need to tell the router where to find your controller. On second thought, maybe that's only if you don't monkey-patch the class as we're doing here... unfortunately the code I was playing around with is on my other computer. I'll check tonight.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, turns out you can't just monkey-patch Devise's class in any straightforward way. So you have to create your own SessionsController class that inherits from Devise's, and then tell Devise where to find it in your routes.

...
end
```

Finally, we have to authenticate each subsequent request:

```ruby
class ApplicationController < ActionController::Base
before_filter :authenticate_user_from_token!

private

def authenticate_user_from_token!
token = request.headers['auth-token'].to_s
email = request.headers['auth-email'].to_s
return unless token && email

user = User.find_by_email(email)

if user && Devise.secure_compare(user.authentication_token, token)
sign_in user, store: false
end
end
end
```

Now, we can get on to configuring the Ember side of things.

## The Authenticator

In order to use the Devise authenticator (see the
Expand Down Expand Up @@ -40,24 +134,6 @@ App.LoginController = Ember.Controller.extend(Ember.SimpleAuth.LoginControllerMi
{ authenticatorFactory: "authenticator:devise" });
```

As token authentication is not actually part of Devise anymore, there are some
customizations necessary on the server side. In order for the authentication to
work it has to include the user's auth token and email in the JSON response for
session creation:

```ruby
class SessionsController < Devise::SessionsController
def create
resource = resource_from_credentials
data = {
auth_token: resource.authentication_token,
auth_email: resource.email
}
render json: data, status: 201
end
end
```

## The Authorizer

The authorizer (see the
Expand All @@ -75,28 +151,3 @@ Ember.Application.initializer({
}
});
```

As token authentication is not actually part of Devise anymore, the server
needs to implement a custom authentication method that uses the provided email
and token to look up the user (see
[discussion here](https://gist.github.com/josevalim/fb706b1e933ef01e4fb6)):

```ruby
class ApplicationController < ActionController::API
before_filter :authenticate_user_from_token!

private

def authenticate_user_from_token!
token = request.headers['auth-token'].to_s
email = request.headers['auth-email'].to_s
return unless token && email

user = User.find_by_email(email)

if user && Devise.secure_compare(user.authentication_token, token)
sign_in user, store: false
end
end
end
```