Rest – Token-based authentication using access and refresh tokens

apiauthenticationdesignrestSecurity

I am implementing a token-based authentication system for a REST API using a short-lived access token and a long-lived refresh token. This is an abstract overview of the relevant API endpoints (HTTPS is enforced for all endpoints):

Endpoints:

POST /register/
POST /login/
POST /logout/
POST /password/change/

Implementation:

POST /register/:

  • Request: Client sends username, email, and password in JSON.
  • Server actions:
    1. Validates input, creates user in database (stores user id, username, email, and password hash).
    2. Creates short-lived access token in JWT format (contains user id, issued date, and expiration date).
    3. Creates long-lived refresh token as a UUID string and stores it in database (stores user id and refresh token).
  • Response: Server returns access token and refresh token in JSON.

POST /login/:

  • Request: Client sends username and password in JSON.
  • Server actions:
    1. Validates input, checks if credentials are valid by checking database.
    2. If credentials are valid, creates short-lived access token and long-lived refresh token as mentioned previously.
  • Response: Same as /register/, returns access token and refresh token in JSON.

POST /logout/:

  • Request: Client sends refresh token in Authorization header as Bearer token.
  • Server actions:
    1. Validates refresh token by checking refresh token database.
    2. Removes the refresh token from the database.
      Note: This leaves the access token valid, but since it will be short-lived (1 hour or so, I think it should be fine).
  • Response: Returns whether the logout request was successfully processed in JSON.

POST /password/change/:

  • Request: Client sends access token in Authorization header as Bearer token, and also sends old password and new password in JSON through HTTPS.
  • Server actions:
    1. Decodes access token to retrieve the user, and checks the user's old password with the database.
    2. Sets password hash of user in the database to new password's hash.
    3. Removes all refresh tokens associated with user in the refresh token database to essentially log out existing sessions (leaves short-lived access tokens valid).
  • Response: Returns whether the password change request was successfully processed in JSON.

Questions:

  1. Is this approach secure? Specifically:
    • Is sending the username and password through JSON safe if done over HTTPS? How would I prevent unauthorized domains from making calls to this endpoint? Furthermore, how would I prevent programmatic logins?
    • Should the refresh tokens be hashed before storing them in the database, or am I just being paranoid?
  2. If the client were a web browser, how would I securely store the refresh token on the client?
    • One idea I have for storing the refresh token is: when the user logs in, in addition to sending the refresh token to the client, the server stores the token in an HttpOnly cookie with a secure flag. Authorization will still be done through the Authorization header, but when the client initially loads up, it can send a GET request to an endpoint that checks if the cookie contains a valid refresh token, and if so, return it to the user in JSON. In other words, the only time the cookie will actually be used is to return the refresh token inside the cookie to the client. Is this approach secure? I think it will prevent CSRF as there are no side effects when requesting the refresh token from the cookie, but is there another way an attacker could intercept the refresh token (assuming HTTPS)?

Best Answer

Is this approach secure? Specifically:

  • Is sending the username and password through JSON safe if done over HTTPS?

Yes. Headers, request params and request body are encrypted during the communication.

Once on the server-side, do not log the request body :-)

  • How would I prevent unauthorised domains from making calls to this endpoint?

You can not. Basically, once the API is on the WWW, it's automatically exposed to all sort of malice. The best you can do is to be prepared and to be aware of the threats. At least about those that concern you. Take a look here.

A possible approach to the problem could be implementing (or contracting) an API Manager.

On-premise API Managers can reduce the attack surface because all the endpoints behind the AM are not necessarily public.

You could achieve the same result with some products in the cloud, but they are absurdly expensive for the mainstream.

Anyways, the API Management endpoints will remain exposed to attacks.

  • Furthermore, how would I prevent programmatic logins?

If by programmatic logins you mean attacks by brute force, a threshold (max number of allowed requests per second) and a black list should be enough to deter the attacker's insistence. For further information, take a look here.

Many of the API Managers provide out of the box API Rate Limit configurations and Whitelists.

If you are familiar with the Google API Console, then you can guess what an API Manager can do.

  • Should the refresh tokens be hashed before storing them in the database, or am I just being paranoid?

Whether the refresh token is a plain UUID or anything else, I don't like to expose this sort of implementation detail. So I would suggest to hash it. To me, the more opaque are the implementation details of the security layer, the better.

Regarding the JWT security, take a look here.

  • If the client were a web browser, how would I securely store the refresh token on the client?

You might be interested in JSON Web Token (JWT) - Storage on client side.

Related Topic