Berbare
Berbare

Reputation: 144

Rails user authentication from native Android app

I am trying to develop a Rails app which will mostly communicate with Android app users. People should be able to sign up and sign in from within the app (they can only perform most requests when logged in).

I've looked through many tutorials on how to do the equivalent of "storing a session" on the app, but all of them recommend using the gem 'devise' and its :token_authenticable, which was deprecated for some time now.

I want serious advice on how to perform something equivalent. From my understanding, the client sends a request with params such as {email: "[email protected]", password: "pw12345"}, I check that they match an existing user and retrieve a string token, which this client will from now on send in every request via headers (such as {"my_app_user_email": "[email protected]", "my_app_user_token":"abcdef123456"}).

I've already set some methods with fake values, such as

def login
  if valid_user?(params[:email], params[:password])
    render json: {user_token: default_user_token}
  else
    render json: {message: 'Couldn\'t login'}, status: 401
  end
end

Where default_user_token is a fixed string, and valid_user? compares with fixed values as well.

I guess my problem is knowing if this is the right approach, and if so, how can I make a User model that creates and validates tokens?

Extra bit of code

def verify_token # This already works by using default values in the Android app code
  email = request.headers['HTTP_MY_APP_USER_EMAIL']
  token = request.headers['HTTP_MY_APP_USER_TOKEN']
  user = User.find_by_email(email)
  user && user.valid_token?(token) # returns true for default_user_token, for now
end

Upvotes: 4

Views: 1329

Answers (1)

Bruno Paulino
Bruno Paulino

Reputation: 5770

I think your approach is correct. you can add an auth_token field to your model and generate a new one as the user logs in. you can generate the the token like that:

def generate_authentication_token!
    begin
        self.auth_token = Devise.friendly_token
    end while self.class.exists?(auth_token: auth_token)
end

than you can create an authentication service that requires the user token to every request like that:

module Authenticable

  #Devise overwritten method
  def current_user
    @current_user ||= User.find_by(auth_token: request.headers['Authorization'])
  end

  def authenticate_with_token!
    render json: { errors: "Not authenticated" }, status: :unauthorized unless user_signed_in?
  end

  def user_signed_in?
    current_user.present?
  end

end

and then, you can create a SessionsController that log in/log out the user using devise:

def create
    user_password = params[:session][:password]
    user_email = params[:session][:email]
    user = user_email.present? && User.find_by(email: user_email)

    if user
      if user.valid_password? user_password
        sign_in user, store: false
        user.generate_authentication_token!
        user.save
        render json: user, root: :user, status: 200, location: [:api, user]
      else
        render json: { errors: "Invalid email or password" }, status: 422
      end
    else
      render json: { errors: "Invalid email or password" }, status: 422
    end
  end

def destroy
    user = User.find_by(auth_token: params[:id])
    user.generate_authentication_token!
    user.save
    head 204
end

I have this repo on github that uses this solution: https://github.com/brunojppb/api_on_rails

Upvotes: 1

Related Questions