Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

API Authentication for user logged in to a Web App server

I am building a Web App and a separate API (so that users can share their collected data with someone if they want to) using Ruby on Rails. The users can log in on the web app and fill data that should be posted to the API Server.

From everything I have read till now, I guess that I can use cookie based authentication to check whether the user is logged in to the Web App. Now let's say that the user wants to post data to the API Server. Since the user is authenticated to the Web App Server, how should the post request be made so that the API knows that it is getting the data from the specific user who is logged in. Also if the user wants to get data from the API that is private to him/her, how should the get request be made for this purpose?

like image 250
Peeyush Avatar asked Dec 19 '13 19:12

Peeyush


People also ask

How to access the web API using basic authentication?

To access the web API method, we have to pass the user credentials in the request header. If we do not pass the user credentials in the request header, then the server returns 401 (unauthorized) status code indicating the server supports Basic Authentication. Achieve Basic Authentication. Follow the below steps for Basic Authentication.

How do I use configure authentication in a sample web app?

To use this article with Configure authentication in a sample web app that calls a web API, replace the sample web app with your own web app. This article focuses on the web application project. For instructions on how to create the web API, see the ToDo list web API sample.

What is authorization for API authentication?

Simply, authorization is when an entity verifies that you have the right to access data or information on a given server. However, as we scour through this topic of API authentication, it’s crucial to understand that we’re discussing a system that only proves identity (API Authentication). Which are the most Common Methods of API Authentication?

How do I authenticate a web application using HTTP?

You can configure your project to use any of the authentication modules built in to IIS or ASP.NET, or write your own HTTP module to perform custom authentication. When the host authenticates the user, it creates a principal, which is an IPrincipal object that represents the security context under which code is running.


2 Answers

When you say Web app server and a separate API server, which needs to talk to each other every time there is an update from a user on your Web app server. All I can suggest you to break them down to 3 entities as rails engine.

  1. Core: Which will hold all your Model and your data logic.
  2. Application: Which will depend on your core engine and have client facing code, mostly controllers and views.
  3. API: Which will again depend on your core engine and have processing logic, API controllers maybe.

Why Core? Because, when you need to update your business logic, it will be just one place: Core Engine.

Now to answer your question further on authenticating API call from your web app server. You need to:

  1. Build the API - Rails Cast and Building Awesome Rails APIS from Collective Idea Blog.
  2. Secure the API - Rails Cast and Looking for suggestions for building a secure REST API within Ruby on Rails.
  3. I prefer OAuth for securing the API calls. For implementing OAuth2 in rails you can use doorkeeper.

Once you're done with securing API, you can implement the authentication logic in your Web application. You can use OAuth2 for authenticating your app from API.

Also, to make your API available only to OAuth calls using doorkeeper: https://doorkeeper-provider.herokuapp.com/#client-applications

P.S.: I prefer json response from the APIs, it's a preferred trend I'd say. ;)

EDIT- postman is a chrome extension for making experimental/fake APIs before you actually write them for your application. It's much faster because you'd know what you finally have to design at the end of the day.

like image 95
Surya Avatar answered Sep 28 '22 04:09

Surya


You could consider the doorkeeper gem for your API authorization. I considered it but decided against it because of complexity and lacking documentation for my use cases. Put simply I couldn't get it working properly.

There is a good article on authentication using warden without devise which should give you a good feel for the moving parts of an authentication system. Devise is not appropriate for API authentication and in fact Devise recently removed the one thing that could be useful for API's which was token based authentication, obviously API's are not part of their roadmap!

I used the guidance in the article referenced above to create my own JSON only Warden strategy that uses an OAUTH 2 Owner Password Credentials Grant type (See RFC 6749) to generate and return a bearer token for use on future API requests. API clients can easily create the JSON to do this kind of authentication to obtain an authorization access token.

I will provide some of the Rails code to get you started below, but you will have to integrate into your specific environment. No warranty offered :)

Warden initializer:

# config/initializers/warden.rb
Dir["./app/strategies/warden/*.rb"].each { |file| require file }

Rails.application.config.middleware.insert_after ActionDispatch::ParamsParser, Warden::Manager do |manager|
  manager.default_strategies :null_auth, :oauth_access_token, :oauth_owner_password
  manager.failure_app = UnauthorizedController
end

Warden strategy for OAUTH 2 password authentication:

# app/strategies/warden/oauth_owner_password_strategy.rb
module Warden
  class OauthOwnerPasswordStrategy < Strategies::Base
    def valid?
      return false if request.get?

      params['grant_type'] == 'password' && params['client_id'] == 'web' && ! params['username'].blank?
    end

    def authenticate!
      user = User.with_login(params['username']).first
      if user.nil? || user.confirmed_at.nil? || ! user.authenticate!(params['password'])
        # delay failures for up to 20ms to thwart timing based attacks
        sleep(SecureRandom.random_number(20) / 1000.0)
        fail! :message => 'strategies.password.failed'
      else
        success! user, store: false
      end

      # ADD HERE: log IP and timestamp of all authentication attempts
    end
  end

  Strategies.add(:oauth_owner_password, OauthOwnerPasswordStrategy)
end

Warden strategy for OAUTH 2 access token authentication:

# app/strategies/warden/oauth_access_token_strategy.rb
module Warden
  class OauthAccessTokenStrategy < Strategies::Base
    def valid?
      # must be a bearer token
      return false unless auth_header = request.headers['authorization']
      auth_header.split(' ')[0] == 'Bearer'
    end

    def authenticate!
      # Use a periodic cleaner instead
      # clean out all old tokens. DOES NOT RUN CALLBACKS!
      Token.expired.delete

      # lookup bearer token
      token = Token.active.first(purpose: 'access', token: request.headers['authorization'].split(' ')[1])
      if token && (user = token.user) && user.confirmed_at
        success! user, store: false
      else
        # delay failures for up to 20ms to thwart timing based attacks
        sleep(SecureRandom.random_number(20) / 1000.0)
        fail! message: 'strategies.oauth_access_token.failed'
      end
    end
  end

  Strategies.add(:oauth_access_token, OauthAccessTokenStrategy)
end

Null authentication strategy (can be useful in development, just set config.null_auth_user within config/environments/development.rb):

# app/strategies/warden/null_auth_strategy.rb
module Warden
  class NullAuthStrategy < Strategies::Base
    def valid?
      ! Rails.configuration.null_auth_user.blank?
    end

    def authenticate!
      user = User.with_login(params["username"]||Rails.configuration.null_auth_user).first
      if user.nil?
        fail! :message => "strategies.password.failed"
      else
        success! user, store: false
      end
    end
  end

  Strategies.add(:null_auth, NullAuthStrategy)
end

Warden failure application for JSON clients (uses a bare metal rails controller):

# app/controllers/unauthorized_controller.rb
class UnauthorizedController < ActionController::Metal

  def self.call(env)
    @respond ||= action(:respond)
    @respond.call(env)
  end

  def respond(env)
    self.status = 401
    self.content_type = 'json'
    self.response_body = { 'errors' => ['Authentication failure']}.to_json
  end
end

Add the following in your base API controller:

before_filter :authenticate!

protected

    helper_method :warden, :signed_in?, :current_user

    def warden
      request.env['warden']
    end

    def signed_in?
      !current_user.nil?
    end

    def current_user
      @current_user ||= warden.user
    end

    def authenticate!(*args)
      warden.authenticate!(*args)
      # ADD ANY POST AUTHENTICATION SETUP CODE HERE
    end

A sessions controller:

class SessionsController < ApiController
  skip_before_filter :authenticate!

  # TODO exceptions and errors should return unauthorized HTTP response.
  # see RFC for details

  def create
    # mandate the password strategy.
    # don't use session store (don't want session cookies on APIs)
    authenticate!(scope: :oauth_owner_password, store: false)

    if signed_in?
      # create access token
      token = Token.create! purpose: 'access',
                            user: current_user,
                            expires_in: Rails.configuration.session_lifetime

       # Ensure response is never cached
       response.headers["Cache-Control"] = "no-store"
       response.headers["Pragma"] = "no-cache"
       response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"

      # send the OAuth response
      render json: {
          access_token: token.token,
          token_type: 'Bearer',
          expires_in: token.expires_in,
          scope: 'user'
      }
    end
  end

  def destroy
    Token.current.delete
    warden.logout
    head :no_content
  end
end

You will need to define your own User and Token models for tracking users and bearer tokens respectively, the Token model needs to have a scope called active to limit the result set to unexpired tokens. Token generation should use SecureRandom.urlsafe_base64

like image 35
Andrew Hacking Avatar answered Sep 28 '22 03:09

Andrew Hacking