22
votes

I am building an API in Yii 1.x which will be used with a mobile application. Part of the process involves a login (with a username and password) using the following JSON request below:-

// Request sent with username & password

{
"request" : {
    "model" : {
        "username" : "bobbysmith",
        "password" : "mystrongpassword"
    }
  }
}

// If successfully logged in return the following response

{
"response": {
    "code": 200,
    "message": "OK",
    "model": {
        "timestamp": 1408109484,
        "token": "633uq4t0qdtd1mdllnv2h1vs32"
    }
 }
}

This token is quite important - once a user is logged in on the app I'd like them to have access to other pages that require them to be logged in. I want the mobile app to store this token & if the same 633uq4t0qdtd1mdllnv2h1vs32 token is found within any subsequent requests it will accept this as being an authenticated request (for this user 'bobbysmith').

I am a little unsure of how to best go about doing this, I have done some research and can oAuth has been mentioned a few times, along with Basic Authentication via HTTPS.

So in a nutshell this...

  1. On mobile app homepage, user logs in correctly with their username & password & this sends a request to the API.
  2. This returns a successful response (shown above) with the current timestamp & the all important token.
  3. The same user goes to another app page/view where this token is a) required and b) if it matches up this authenticates that user (e.g so they can edit that account etc..)
  4. Once user clicks 'Logout' this token is then removed (and can longer access My Account etc..) - essentially a token based authentication system.

Can anyone possibly explain the best way to achieve this? Please let me know if what I have stated isn't 100% clear and i'll provide more information.

While I am using PHP, a Yii 1.x solution is ideal as that is what the current API is built using.

In a nutshell, the app ensures that every request to server includes token in the payload or header so this token can be retrieved on every subsequent post, once logged out this token is simply removed OR set to null/empty

3

3 Answers

56
votes

Information about handling security interfaces

Focus a solution which provides all the good (RESTful) auth stuff at once, which probably will be:

  • SSL (most IMPORTANT, else "HTTP-Auth" would be sence less, everyone would be able to read out your Request Header / Body Man-in-the-middle-attack)
  • oAuth (or better oAuth2!)
  • HTTP-Auth
  • Tokens (including a limited lifetime, refresh and maybe include IP/DeviceUID check logic-> if it's mobile!)
  • Salt generated passwords
  • Custom HTTP-Headers for ID/ENV/CLIENT checks or what ever.
  • Encrypted body data for Request and Response to prevent data manipulations

Hint: Personal user data should be allways encrypted!


Maybe a solution

Above you can see the standard information about security interfaces. To ensure lasting security you can try it like in the next part. I'am not sure about your AppSidePersitence. Maybe its sqlLite or something like that. That's why I don't indicate a code-based DB-Schema, like I did it to Yii. You will need a storage/persistence inside your Yii application (backend) and also inside your app (client) to store times and tokens.

Your YiiDBModel

enter image description here

-- -----------------------------------------------------
-- Table `mydb`.`user`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`user` (
  `id` INT NOT NULL,
  `username` VARCHAR(255) NOT NULL,
  `password` VARCHAR(255) NOT NULL,
  `lastLogin` DATETIME NULL,
  `modified` DATETIME NULL,
  `created` DATETIME NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;

----------------------------------------------------
-- Table `mydb`.`authToken`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`authToken` (
  `id` INT NOT NULL,
  `userId` INT NOT NULL,
  `token` VARCHAR(255) NOT NULL,
  `created` DATETIME NOT NULL,
  PRIMARY KEY (`id`, `userId`),
  INDEX `fk_authToken_user_idx` (`userId` ASC),
  UNIQUE INDEX `token_UNIQUE` (`token` ASC),
  CONSTRAINT `fk_authToken_user`
    FOREIGN KEY (`userId`)
    REFERENCES `mydb`.`user` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

Your AppPersitenceModel

enter image description here

Handling your token right and ensure login security

  1. Any token you generate on Yii-Application side will be stored with a "created"-Datetime in your YiiApp-Database (see table authToken). Each user has only one "token", which will generated after a correct "login"-Request. In that way, you don't need to check tokens while "login". Defined by the schema, "token" is unique! I think there is no need to handle historical data (old tokens which are expired).
  2. Once the user-login was validated as "success" by Yii you generate a new user-token with current timestamp, which will be stored in your YiiApp-DB. In your YiiApp you need to configured a "expire time", which will be added to the current timestamp, for example, if you like to use "timestamps": Current timestamp is: 1408109484 and your expire time is set to 3600 (which is 3600 sec = 1h). So ... your expire datetime which will be send via API is (1408109484+3600). Btw. Hint: You don't need to send attributes like "code": 200. Response-Codes are included in your Requests/Response-Header-Data.

    ** 200 OK Response-Example, after user-login was successful, holds the calculated "expired"-date:**

     {
        "error": null,
        "content": {
            "expires": 1408109484,
            "token": "633uq4t0qdtd1mdllnv2h1vs32"
        }
    }
    
  3. Important: Every requests which you want to be secured, needs to be send with your generated User-"token". Which will probably stored in your deviceStorage. You can handle your "login-states"- really RESTful if you using HTTP-Response-Codes right, for example, 200 OK (if all is fine) or 401 (not authorized, user is not loged in or session is expired). You need to validate your User-Request's on Yii side. Read out the token from incoming requests, validate it due to given tokens in database and compare "created"-DB with the current incoming Request-Time (HTTP-Requests).

    ** Request-Example, default schema on any security requests:**

    {
        "token": "633uq4t0qdtd1mdllnv2h1vs32"
        "content": {
            "someDataYouNeed" : null
        }
    }
    

    ** 401 Unauthorized Response-Example, token expired :**

    {
        "error": 1, // errorCode 1: token is expired
        "content": {
            "someDataYouNeed" : null
        }
    }
    

    ** 401 Unauthorized Response-Example, user is not logged in (no token exists in YiiDB):**

    {
        "error": 2, // errorCode 2: user is not logged in
        "content": {
            "someDataYouNeed" : null
        }
    }
    
  4. Keep a User-Session alive? That's pretty easy. Just update "created"-Date in authToken-Table to the current request time. Do that every time, a valid request was send by the user. In that way, the session will not expire, if the user is still active. Ensure, your DB-Token is not expired, before updating expires-Date field in DB. If no request be send while the session expires, the keep alive won't be possible anymore.

    Sorry, but adding PHP-Codes would be too much.

1
votes

By this time probably you switched to Yii2, and for future reference, the cleanest solution would be to use included classes for RESTful APIs, or one can implement them in any framework.

Source: HttpBearerAuth.php

The advantages are explained fully in this article, but to summarise, it's better to use your solution with request headers, since GET parameters might be saved in logs and Basic Auth password can be easily intercepted if you don't use SSL (you should!)

0
votes

If you are building for a native mobile app then sensible thing would be to rely on the security of the native memory (eg the iOS keychain) and not a cookie based solution. Otherwise how you have described seems fine. As long as your payload is being sent over SSL it doesnt really matter if the token is in the PUT or the POST. Your token management (ie expiration times) are business decisions you have to make. Back end I would do as you describe and hold the token in your database and delete it when it has become defunct for whatever reasons and return a message to your client app to put it back into logged out mode/re-request credentials.

EDIT: Check out this awesome tut from the prolific Phil Sturgeon. He also has a great CI library for building RESTful API's in CI which might be worth looking at.

http://philsturgeon.uk/blog/2013/07/building-a-decent-api

http://code.tutsplus.com/tutorials/working-with-restful-services-in-codeigniter--net-8814