Sometimes an application needs to call an endpoint that makes us lay awake at night thinking about what would happen if someone manage to steal an access_token and call this endpoint to either steal sensitive data or make changes to data. An example could be adding subscription data to a user. We don’t want the user to be able to do this by himself if he has managed to pick up his access_token from some insecure cookie or something like that.
The solution we have chosen for this problem is to use signed requests in these cases. Our implementation is very inspired by Instagram's secure requests. But also introduces a timestamp parameter that is required in order to protect against replay attacks (eg. performing old requests from a log).
How the signing works
First we’ll look into how the signing works in theory. Then we’ll look at an example.
Parameters
Title
Title
label
Description
sig
This is the signature, a signed request_token with your Application’s client_secret using the SHA256 hash algorithm.
timestamp
An ISO8601 timestamp of when the request was made from the application.
Creating the timestamp parameter is easy. In Ruby you could do it like this:
timestamp =Time.now.iso8601
The sig parameter is more difficult. First you need to create a request_token, a string representing the request. Then this request_token must be signed using SHa256 hash algorithm using the same client_secret that the application use for getting access_tokens during OAuth Authorization.
Generating the request_token
The request_token is a string that represents the current request the application is trying to perform on one of aID’s service endpoints. It’s a combination of the URL, parameters and posted fields in the request.
Definition: request_token: API endpoint URL appended with a concatenation of all key/value pairs of your request parameters (or posted fields), including timestamp (but not sig, that would cause pain), sorted by key in ascending order. URL and each key/value pair are separated by the pipe character.
The same algorithm is performed on the server, so both the server and application will sign the same request_token with the same client_secret. So let’s have a look at how we do the signing.
So this should now be sent as the parameter (or posted field) called sig in the request.
Putting it all together
So this is an example in Ruby that puts all the concepts together and results in a curl-command that could be used in a terminal to do the actual request:
So this is the request the application should make to the service endpoint in aID. The endpoint will perform the same logic to generate and sign the request using the secret aID knows for the application owning the access_token in the request. If this succeeds, the request is performed. If not, the response will be an error response.
Error responses
Signature is invalid
If the provided sig parameter can not be recreated exactly by the server, the following response will be returned:
HTTP/1.1 403 Forbidden
{
"errors":[
{
"id":"53225f86-c5e3-49bb-a5d8-daa740445ebb",
"meta":{},
"code":"request.access.signature.invalid",
"status":"403",
"title":"Signature does not match request or secret",
"detail":"Provided signature does not match using the application secret and request URL with parameters (included posted fields)"
}
]
}
Timestamp format is invalid
If the server does not understand the timestamp parameter, the response will look like this:
HTTP/1.1 400 Bad Request
{
"errors":[
{
"id":"b64b96d1-a130-4b25-a466-d5d3bf1945bf",
"meta":{},
"code":"request.access.timestamp.invalid.format",
"status":"400",
"title":"Timestamp format is invalid",
"detail":"Timestamp must match ISO8601 format, like this: 2016-01-28T15:25:16+00:00"
}
]
}
The server will use the Time.iso8601 method in Ruby to decode the parameter, so the service will support whatever this method supports.
Timestamp is no longer valid
If the timestamp is not considered valid by the server, the response will look like this:
HTTP/1.1 403 Forbidden
{
"errors":[
{
"id":"b64b96d1-a130-4b25-a466-d5d3bf1945bf",
"meta":{},
"code":"request.access.timestamp.invalid",
"status":"403",
"title":"Timestamp not currently valid",
"detail":"Provided timestamp is not valid, current time on server is: 2016-01-28T15:25:16+00:00"
}
]
}
Exact time synchronization is virtually impossible, so there is some slack here. The detail in the error response will provide some helpful hint about what the server considers is the correct time. This should match the time on your server, if not someone needs to read up on NTP.
Missing parameter
If you forget to submit either timestamp or sig, you will get one of these responses: