3

Authentication

The authentication is going to be signature-based. The signature will be generated using:

HMAC_SHA256(SHA1(secret_key) + '#' + request_data + '#' + utc_timestamp)

The utc_timestamp will also be included into the X-Timestamp header or into the URL using the _timestamp parameter. The request_data will contain all parameters (URL and POST) and API-related headers. All these data will be lowercased, sorted and joined using = and &.

Generally, the REST API will support three authentication schemas:

  1. API keys based – the signature will include the special secret key associated with the particular API key. Will use the X-API-Key header or the _api_key URL parameter and the X-API-Signature header or the _signature URL parameter.

  2. User credentials based – the signature will use the password as the secret key. Will use the X-API-Signature header or the _signature URL parameter and the staff_name URL parameter.

  3. Session based – the signature will use the user's password or a specially generated code (more details below)... Will use the X-Session-Signature header or the _session_signature URL parameter and the X-Session-ID header or the _session_id URL parameter.

Please note, that the specification allows to use User credentials and Session at the same time (i.e., their headers and parameters do not conflict).

Session

Disclaimer: Yes, I know – sessions are not RESTful, as they are stateful... However, our product requires sessions for some functionality, and in the interests of simplicity we want to maintain one API/protocol – so I am not looking to get into a RESTful debate. I prefer to think, that a session is just a resource, which you need to maintain while working with the API.

The session will be just a resource: /api/v1/session.

So, the standard procedure:

  1. To create a session a client application will need to send a POST request using the staff credentials authentication, which has been described above: POST /api/v1/session&staff_name=s-andy.

  2. The server will reply with 201 Created and will deliver the session id in the response body.

  3. Afterwards the client application will use this session id and the staff password to access API.

Having gotten a request with particular session id the server will update the last access time of the appropriate session resource.

But our users will also have an option to use the two-factor authentication. In the UI after logging in such users will be requested to enter the verification code, which will come to their mobile devices. When designing the authentication I thought that it would be great to have some special 'secret' instead of the user password for the session. Then it dawned upon me – why not using this verification code?

So the flow for two-factor authentication will be:

  1. A client application sends a POST request using the staff credentials.

  2. Server initiates the verification code generation and delivery and returns 202 Accepted with the session id, but the session is not verified yet.

  3. Within 30 seconds the client application sends any request using the session id in the X-Session-ID header or in the_session_id URL parameter and the verification code as the secret for generating the signature.

  4. Having gotten such request the server updates the session making it verified and saves the verification code (a.k.a. one-time password) as the secret key for this session.

  5. Afterwards the client application will use the session id and the verification code (as the secret key) to access API.

  6. When the session times out, and, therefore, gets deleted, or when the user deletes the session (i.e., performs logout), the session id and the "one time password" become unusable.

I wanted to use this community of experts as a sounding board for this two-factor authentication idea; can you see any pitfalls that I cannot?

3 Answers 3

5

The problem is that what you call verification code is actually a Two-Factor Token. Tokens are normally short (like 6-7 characters).

So you'll do:

HMAC_SHA256(SHA1(TOKEN) + '#' + request_data + '#' + utc_timestamp)

And token is too short.

I also don't understand what a verified session_id vs an unverified session_id looks like.

The biggest problem is that this authentication scheme seems unnecessarily complex. Complexity and security are mostly opposites.

The way we suggest our customers (www.authy.com) to do two-factor auth is:

  1. User sends a POST request with their credentials.

  2. Server validates credentials. If correct the server generates a 256 bit string and saves it on to the database as well as returns it back to the browser. We'll call this string "otp".

  3. The user is petitioned his two-factor token.

  4. In the POST request of step 3 there will be:

    hidden_field otp -> which comes back from the server on step 2
    text-field two-factor-token -> which the person gets from his mobile app

  5. At this point on your server you verify the two-factor token and the otp. If correct you create the session and reset the otp so it can't be used again.

8
  • That's why we are going to use SHA1 for the token - this will make it longer.
    – Andriy Lesyuk
    Mar 15, 2013 at 0:01
  • An unverified session is the session, which waits for the two-factor token to be submitted (used). A session becomes verified, if either the verification code has been provided or it does not require it.
    – Andriy Lesyuk
    Mar 15, 2013 at 0:03
  • In fact, your steps 1-4 are almost exactly the same as I've described. The difference is that we are not going to submit two-factor token as it is but will use it for creating signature. Also here you do not cover how do you authenticate the session - we are going to do this using the two-factor token as the secret for creating the signature.
    – Andriy Lesyuk
    Mar 15, 2013 at 0:04
  • The reason I don't cover how to verify the session is that I recommend to never generate a session until both password and two-factor token are verified.
    – daniel
    Mar 15, 2013 at 0:04
  • 1
    The reason you would'nt brutforce the password is that it requires a POST request and you can actually limit them easily, like max 3 tries. In this case the person would just generate different cookies and point them to different GET resources. So they can bypass your authentication flow and i'll be very hard to rate limit them.
    – daniel
    Mar 15, 2013 at 1:00
1

Your verification code sounds a lot like the way google two step verification works. If it is secure enough for google accounts then it will probably work for you.

The big question is how you are going to deliver the verification code to user? It is the classic problem of symmetrical key encyrption systems.

Also, what language are you implementing this in? Have you found any open source solutions that do these kinds of things? For instance in c# ServiceStack Auth does the same kinds of things with credentials and sessions.

1
  • The verification code will be delivered to user's mobile phone mostly. The authentication will be implemented in PHP. Currently we look for the design and afterwards we will think on implemenation details.
    – Andriy Lesyuk
    Mar 14, 2013 at 23:47
-1

It is important to ask yourself if you really need to use sessions? What are you going to store in the session? Do you have any client context data (eg. shopping cart) that does not represent a domain object that is persisted? If so, a RESTful service is probably not the best choice. If not, rather than store client context on the server can you store it on the client?

With that in mind I would go with option 2 (User credentials). Sorry for posting this same response for multiple questions but one such approach is as follows:

Say you have a mobile client app; first make the user sign up or register by providing their email(username) and password in a separate web form (not part of the app or REST service). Then upon successful registration you respond with a user key (this can be a shared secret key stored in the user account on the server (database) for symmetric encryption or a public key for asymmetric encryption where the corresponding private key is stored in the user account on the server (database). This is all done using a web form over SSL.

Now when the user opens the client app you must ask them for their credentials which will be sent with every request to the RESTful service. They must provide their name, password and encryption key which they received previously. This need only be done once. The app then provides some http header with each request which looks something like this:

AUTHENTICATE> username:timestamp:encrypted{password:timestamp} /AUTHENTICATE>

Note that both the password and timestamp inside the {} is encrypted using the user's key. The timestamp is updated with every single request.

Implement an authentication filter on the server that does the following:

First check the timestamp and if expired (say older than 1 second) send an UNAUTHORIZED HTTP response code. If the timestamp is valid lookup the username in your user account database. If not found send an UNAUTHORIZED HTTP response. If the username is found, fetch the stored encryption key for that user (remember this can be a shared secret key or the private key for the users public key). Decrypt the encrypted {password:timestamp}. The decryted password must match the users password stored in your database (the password itself could also be encryted in the database using another key for added security) and the decrypted timestamp must also match the non encrypted timestamp sent in the AUTHENTICATE header above. If not then send an UNAUTHORIZED HTTP response code. If successful the request has been authenticated without the use of cookies/sessions.

You can also cache the the user details to avoid doing a database lookup with every request. Furthermore you could use the same key to encrypt any sensitive data that is sent back to the client in the response and flag it so that the client knows to decrypt it.

Now if someone is snooping and intercepts the request they will not be able to re-use it to gain access because either the timestamp will be invalid or ,if they update the unencrypted timestamp to be valid, it will not match the encrypted timestamp (after the authentication filter decrypts it).

Another advantage of this approach over using a single app key is that you now have complete control over who can access your service by putting an expiry date on the user account in the database (effectively implementing a subscription based service). This is great because at first you may want to get as many users as possible with a trial subscription (free for say 1 year) then later block access to that user if they haven't payed up to extend the account expiry date :)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .