Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing simple authentication for PHP REST API [closed]

I am working on adding a REST API to a legacy PHP site. This is to provide an endpoint for an internal app, so I am quite free in how I design things and what do and don't support.

What I now need to add to this API is a way to login, and then perform actions as a specific user. The site has been built years ago and not necessarily with the best practices at the time, so I am unfortunately a bit restricted in how I do this. All of this needs to run in PHP 5.4 with MySQL 5.6.

I have been reading up on common designs for this and OAuth1/2 looks like the most common standard. However, this seems like massive overkill for my purposes, since it has various features that I do not need and seems very complicated to implement.

Instead, I am planning on just doing something like this:

  • The client calls a get_session API endpoint, which generates a random session ID, saves that to a table in the database and returns it to the client.
  • The client saves this session ID.
  • Then the client authenticates by sending a request to the login endpoint, sending the username, password and session ID (via HTTPS obviously).
  • The server compares the data to the user table and, if the login is correct, updates the session table to associate the session ID with the corresponding user ID. This needs to be rate-limited in some way to prevent brute forcing.
  • Now the client can call any other endpoints providing only its session ID for authorization.
  • On each request, the server looks up the session ID, sees which user it has been associated with and performs the correct action.
  • The client can remember the session ID for future use, until it either gets removed manually or expires after some amount of time.
  • To log out, the client sends a request to the logout endpoint and the server removes the association with the user account.

Is this a reasonable design? It's obviously not very sophisticated, but I am looking for something that I can implement without a huge hassle or requiring third-party libraries.

like image 540
Nils Avatar asked Oct 12 '17 22:10

Nils


People also ask

How authorize API in PHP?

Client Credentials Flow First, turn on the Client Credentials grant on then Advanced settings > Grant Types tab on the Application settings page. Next, authorize the Application for the API being used on the Machine to Machine Applications tab on the API's Settings page.

What is the best authentication method for REST API?

OAuth (specifically, OAuth 2.0) is considered a gold standard when it comes to REST API authentication, especially in enterprise scenarios involving sophisticated web and mobile applications. OAuth 2.0 can support dynamic collections of users, permission levels, scope parameters and data types.

How do I set basic authentication in HTTP header PHP?

Once the user has filled in a username and a password, the URL containing the PHP script will be called again with the predefined variables PHP_AUTH_USER , PHP_AUTH_PW , and AUTH_TYPE set to the user name, password and authentication type respectively. These predefined variables are found in the $_SERVER array.


1 Answers

One of the major points of REST as a concept is to avoid the use of session state so that it's easier to scale the resources of your REST endpoint horizontally. If you plan on using PHP's $_SESSION as outlined in your question you're going to find yourself in a difficult position of having to implement shared session storage in the case you want to scale out.

While OAuth would be the preferred method for what you want to do, a full implementation can be more work than you'd like to put in. However, you can carve out something of a half-measure, and still remain session-less. You've probably even seen similar solutions before.

  1. When an API account is provisioned generate 2 random values: a Token and a Secret.
  2. When a client makes a request they provide:
    • The Token, in plaintext.
    • A value computed from a unique, but known value, and the Secret. eg: an HMAC or a cryptographic signature
  3. The REST endpoint can then maintain a simple, centralized key-value store of Tokens and Secrets, and validate requests by computing the value.

In this way you maintain the "sessionless" REST ideal, and also you never actually transmit the Secret during any part of the exchange.

Client Example:

$token  = "Bmn0c8rQDJoGTibk";                 // base64_encode(random_bytes(12));
$secret = "yXWczx0LwgKInpMFfgh0gCYCA8EKbOnw"; // base64_encode(random_bytes(24));
$stamp  = "2017-10-12T23:54:50+00:00";        // date("c");
$sig    = hash_hmac('SHA256', $stamp, base64_decode($secret));
// Result: "1f3ff7b1165b36a18dd9d4c32a733b15c22f63f34283df7bd7de65a690cc6f21"

$request->addHeader("X-Auth-Token: $token");
$request->addHeader("X-Auth-Signature: $sig");
$request->addHeader("X-Auth-Timestamp: $stamp");

Server Example:

$token  = $request->getToken();
$secret = $auth->getSecret($token);
$sig    = $request->getSignature();

$success = $auth->validateSignature($sig, $secret);

It's worth noting that if decide to use a timestamp as a nonce you should only accept timestamps generated within the last few minutes to prevent against replay attacks. Most other authentication schemes will include additional components in the signed data such as the resource path, subsets of header data, etc to further lock down the signature to only apply to a single request.

2020 Edit: This is basically what JSON Web Tokens [JWT] are.

When this answer was originally written in 2013 JWTs were quite new, [and I hadn't heard of them] but as of 2020 they've solidly established their usefulness. Below is an example of a manual implementation to illustrate their simplicity, but there are squillions of libs out there that will do the encoding/decoding/validation for you, probably already baked into your framework of choice.

function base64url_encode($data) {
  $b64 = base64_encode($data);
  if ($b64 === false) {
    return false;
  }
  $url = strtr($b64, '+/', '-_');
  return rtrim($url, '=');
}

$token  = "Bmn0c8rQDJoGTibk";                 // base64_encode(random_bytes(12));
$secret = "yXWczx0LwgKInpMFfgh0gCYCA8EKbOnw"; // base64_encode(random_bytes(24));

// RFC-defined structure
$header = [
    "alg" => "HS256",
    "typ" => "JWT"
];

// whatever you want
$payload = [
    "token" => $token,
    "stamp" => "2020-01-02T22:00:00+00:00"    // date("c")
];

$jwt = sprintf(
    "%s.%s",
    base64url_encode(json_encode($header)),
    base64url_encode(json_encode($payload))
);

$jwt = sprintf(
    "%s.%s",
    $jwt,
    base64url_encode(hash_hmac('SHA256', $jwt, base64_decode($secret), true))
);

var_dump($jwt);

Yields:

string(167) "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6IkJtbjBjOHJRREpvR1RpYmsiLCJzdGFtcCI6IjIwMjAtMDEtMDJUMjI6MDA6MDArMDA6MDAifQ.8kvuFR5xgvaTlOAzsshymHsJ9eRBVe-RE5qk1an_M_w"

and can be validated by anyone that adheres to the standard, which is pretty popular atm.

Anyhow, most APIs tack them into the headers as:

$request->addHeader("Authorization: Bearer $jwt");
like image 134
Sammitch Avatar answered Oct 08 '22 17:10

Sammitch