3

I'm making an API and I'm new into it. I thought of this idea for creating an access token. Basically, we encrypt JSON data with a key, turn it to Base64 and return it as the access token. Later, API decrypts that access token and can use the id. E.g.:

login method: we check if username/password is correct, if it is, then we get user's id from the database. Then we construct the access token, which will be used in the future API calls, like this:

  1. we make a JSON string, e.g. {"user_id":609, "logintime":"1430428180"}
  2. we use encrypt function(e.g. these) and encrypt that JSON string using our encryption key.
  3. we turn encrypted string to Base64, so it is readable and returns it as the access token.

Later, when the user makes an action, we can use this access token to know about the user. E.g.:

update user info method: we send data, which needs to be updated and access token, which we got earlier. Then we:

  1. Base64 decode access token.
  2. decrypt it using our encryption key.
  3. parse JSON data and we know user's id, so we can easily update the database. Also, we know API call is legit because the decrypted string was JSON and it contained user_id and logintime fields.

Is this approach any good?

Manikandan C
  • 668
  • 1
  • 9
  • 22
Gintas_
  • 4,940
  • 12
  • 44
  • 87
  • One advantage of your approach is ease of scalability. You can add or remove servers without them having to exchange session data. – phatfingers Jul 16 '17 at 19:59

2 Answers2

12

In this answer I'm going to focus more on API security, which I feel is the real question behind the question (prompted by OPs comment on my first answer).

The Purpose of API Keys

API keys are used in cases where a user logging in is difficult, impossible, or otherwise undesirable. The API key is used for Authentication, the "proof of who I am" part of API access. Authorization, or the "I am allowed to do this" is usually stored on the server.

Criteria for Valid API Access

In order for you to be satisfied that a request is valid, you must consider a couple of points.

  1. Did the request originate from an authorized user?
  2. Has the request been tampered with in transit?

In order to make sure point 1 holds true over time, an API should also be concerned that a secret key stays secret.

  1. This request does not damage the validity of future requests.

Please note that although API keys are usually sent as HTTP headers, I'm going to be using GET variables in my examples for simplicity.

Single Key API

Some APIs are set up to give access with a single key. Let's use CEgJu8G483u3 as our example key.

The user would send over a request like

GET /users?apiKey=CEgJu8G483u3 HTTP/1.1
Host: api.bigorg.com

If the API is communicating over HTTP, this is inherently insecure:

  1. The key is publicly readable, (and therefore usable) by any nosy 3rd party.
  2. The 3rd party can tamper with the body of the request as much as they like, and the server is unaware.

So in single key API authentication over HTTP, security criteria 1 - 3 violated.

If your single key API is communicating over HTTPS only, then 1 - 3 are still valid and can be considered safe.

Digest, or Signature APIs

This approach is used by Google and Amazon for access to many of their APIs.

When the server grants access to their system, two keys are generated, an identifier and a secret.

ACCESS_IDENTIFIER = 'bill';
ACCESS_SECRET = 'U59g5LcBBZpw';

Once the two keys have been generated, the client and server store a copy of the secret, which is never directly transmitted again.

When a request is made under this system, the identifier is provided with the request (note that the access key can be as complicated as a randomly generated string, or as simple as the id of the user trying to access the system.

Just before the request is sent by the client, a digest of the request is generated and then encrypted using the previously agreed secret. The methods of generating the signature vary, but a common one right now is HMAC.

The request then looks like this

     GET /users?identifier=bill&signature=JjU1j1fIH62KG7FCTdZGzK7J HTTP/1.1
     Host: api.bigorg.com

Random pseudo-signature used in this example

If the request body is included when generating the signature, you can now authenticate safely.

Note that listening to HTTP traffic is still very very simple, so unauthorized knowledge will still be a problem, but the listening party cannot send their own requests or tamper with yours.

If the request is tampered with, the signature becomes invalid and the request will be rejected.

JSON Web Tokens (your solution)

Your solution is nearly identical to JSON Web Tokens, so I'll just pretend you're using that.

This solution is secure over HTTPS. (DO use HTTPS)

  1. The request comes from a valid user => They knew the token
  2. The request has not been tampered with => HTTPS prevents a 3rd party from tampering with a request
  3. The access key remains a secret => HTTPS prevents a 3rd party from reading your access key

If your API is using HTTP/plaintext, you can use your JSON Web Tokens as the access key, but you'll need to also generate a private secret and use that to add a signature to the request.

Note that I'm not sure how you'll transmit the secret to the client if you're using HTTP. Maybe via snail mail or email?

Conclusion

Your idea has been replicated by very smart people. Congratulations! This means it was a good idea!

Your access key solution is solid provided that you use HTTPS.

JoshWillik
  • 2,624
  • 21
  • 38
0

When in comes to authentication, access tokens, what have you, you're generally better off using a proven library that someone far smarter than the both of us has created.

Json Web Tokens or JWT, were created to solve exactly this sort of problem (among others). And it has a PHP library.

So creating an access token for a user would look like the following. (Paraphrasing the Readme)

<?php

$key = "my-secret-key-here";
$token = array(
    "user_id" => 1
);

$jwt = JWT::encode($token, $key);

/**
  * $jwt is your JSON web token, it will look something like this:
  *
  * eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMSJ9.TcXz_IwlmxO5nPd3m0Yo67WyYptabkqZW4R9HNwPmKE
  *
  **/

$decoded = JWT::decode($jwt, $key, array('HS256'));

print_r($decoded);

/*
 NOTE: This will now be an object instead of an associative array. To get
 an associative array, you will need to cast it as such:
*/

$decoded_array = (array) $decoded;

?>

So in answer to your question, you're on the right track! JWT seems to do exactly what you're describing here, so I'd opt for the safety of a more thoroughly tested library.

JoshWillik
  • 2,624
  • 21
  • 38