Rails API - Authentication with JWT

#51 Rails API - Authentication with JWT
10/23/2016

Summary

Using the knock gem, we will add JWT Authentication to our Rails API Application.
7
rails api json authentication

Summary

Gemfilegem 'knock'
Bashrails generate knock:install
rails generate knock:token_controller user
api/application_controller.rbmodule Api
  class ApplicationController < ActionController::API
    include Knock::Authenticable
    undef_method :current_user
  end
end
user.rbclass User < ApplicationRecord
  ...
  alias_method :authenticate, :valid_password?

  def self.from_token_payload(payload)
    self.find payload["sub"]
  end
end
api/users_controller.rbmodule Api
  class UsersController < Api::ApplicationController
    before_action :authenticate_user
    ...
  end
end
cURL Authenticationcurl -X POST "http://api.demo.dev/user_token" -d '{"auth": {"email": "[email protected]", "password": "123456"}}' -H "Content-Type: application/json"

{"jwt":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NzczNjU5MjYsInN1YiI6MX0.77pG0_NrD8neDRqA-lHGfLdc8Xs65oPW1CL5lXmzx40"}
cURL GET Usercurl -H "Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NzczNjU5MjYsInN1YiI6MX0.77pG0_NrD8neDRqA-lHGfLdc8Xs65oPW1CL5lXmzx40" http://api.demo.dev/users/1

{"id":1,"first_name":"John","last_name":"Doe","email":"[email protected]","edit_link":"http://api.demo.dev/users/1/edit","phones":[{"name":"Home","phone_number":"888-555-1234"}]}
00000000000000000000000000000000?d=mm&f=y&s=64
[email protected] said about 2 months ago:

Thanks Dave for making this video. I've question about the login. That means, for the very first client request to server, the client actually sending a plain text password. Right? How secure is that? If I'm not mistaken, everything that client sends to server will be show up inside the server logs. Please clarify.

635114?v=3&s=64
kobaltz said about 2 months ago:

By default, config/initializers/filter_parameter_logging.rb will filter the password

Rails.application.config.filter_parameters += [:password]

So, the logs would filter out the password and never be displayed in the logs. Whenever communicating with API, especially sending the password, you should always encrypt the communication with SSL. This is really no different than sending a POST request to a web login session. Unless the form is posted to an endpoint over SSL, the password would also be sent over plaintext.

Great questions!

635114?v=3&s=64
kobaltz said about 2 months ago:

If your application's traffic is not being served over SSL, anything that is sent or posted, would be essentially in plain text.  It was just illustrating the point that your worry about the API sending the plain text password would be the same worry for a login form. Unless the API endpoint as well as the login form are served over SSL, the password would have been sent over plaintext (and not encrypted via SSL). I suppose the confusion was plaintext. Technically, regardless, in both instances the password is sent as plaintext, but when served over an SSL connection, the plaintext password is protected.

297774?v=3&s=64
rome3ro said about 1 month ago:

Do you have some approach around user register? I found some tutorials about jwt but it's not clear to me, how should be having an api rail app along with an web client rails app, my intention is to have an api and 2 clients, the admin app and the customer app both of them communicating to the API, so the register part needs to be on clients.

635114?v=3&s=64
kobaltz said about 1 month ago:

With a user registration, it would function very similar to any other kind of form post that you would do. You should have some sort of registration endpoint within your API application that would create the user. You may have something like http://example.com/users/create where you would make an HTTP POST to via your app. You would pass in the parameters

{
  user: { 
    first_name: 'John',
    last_name: 'Doe',
    email: [email protected]',
    password: '123456',
    password_confirmation: '123456'
  }
}

and your model would have the necessary validations to ensure password complexity, and email uniqueness. If the user is created, pass a successful response. Otherwise, you would respond with an error.

You would also need to provide an authenticate method for your user. In this example, we simply used devise to handle the user registration and authentication. While you could do something similar in an API only application, it might be a bit heavy. However, using the devise logic (not necessarily the views), will give you access to a lot of the built in functionality around locking out the user, confirmation signups, etc.

Depending on the architecture of your application, you can make the new user registration only available under the client portal. Also, if you are using some sort of role based authorization, make sure that you set the default access to a client role. 

Hope this helps!

635114?v=3&s=64
kobaltz said 23 days ago:

The reason why it is displaying the page instead of a JSON response is due to the sample application's routes.

  namespace :api, path: '/', constraints: { subdomain: 'api' } do
    resources :users
  end
  constraints subdomain: ['', 'www'] do
    resources :users do 
      resources :phones
    end
    root 'users#index'
  end

In the example application's routes, there is a constraints on the subdomain where if it is empty, it will default to the web view of the application. If you're using an API only, you can remove the constraints block as well as the constraints on the subdomain: 'api'. So your routes may look something like

Rails.application.routes.draw do
  post 'user_token' => 'user_token#create'
  devise_for :users
  namespace :api, path: '/' do
    resources :users do 
      resources :phones
    end
  end
end

00000000000000000000000000000000?d=mm&f=y&s=64
[email protected] said 16 days ago:

First thanks for this video. Helps a lot. 

But I am having a trouble implementing this like.

I have the following Routes:

Rails.application.routes.draw do
    namespace :api, constraints: { subdomain: 'api' }, path: '/' do
        namespace :v1 do
            post 'user_token' => 'user_token#create'
        end
    end
end

With this code, I expect to be able to make a POST request like: POST http://api.domain.com/v1/user_token

But when I try to that, I receive an error: NameError: uninitialized constant API::V1::User

It seams like, its looking for the model `User` on the same namespace. But my model User is not namespaced:

class User < ApplicationRecord
    has_secure_password
end

How can I make it work? What I am missing?

Thanks

Login to Comment