DEV Community

Mohamed Said
Mohamed Said

Posted on • Originally published at divinglaravel.com

Authentication and Laravel Airlock

HTTP requests are stateless. To authenticate a user, you need to explicitly mention who the user is on every request. This can be done by sending a token that has information about the user, or sending a session ID that the server can use to find the user.

Tokens are a flexible way to authenticate, but you need to worry about where on the client side you want to securely store that token. Specially if the client is a JS application. On the other hand, sessions are stored on the server side so they are more safe. However, you need to worry about the storage size and the fact that it's only available to applications running on the same root domain.

Laravel Airlock

Airlock is a lightweight authentication system for Laravel. You can use it to ensure requests to your API have a valid token or authentication session.

Consider a JavaScript frontend hosted on the same domain of the API, or a subdomain. With Airlock, you can authenticate requests to your API routes using the regular stateful web guard. Your frontend will need to make a POST request to the /login route and if the credentials are correct, Laravel will store a session containing the user ID that'll be used for authenticating all future requests.

What Airlock does is make sure your API routes are stateful if the requests are coming from a trusted source. Inside the EnsureFrontendRequestsAreStateful middleware, Airlock checks if the request is coming from a domain that you've previously configured in a airlock.stateful configuration value. In that case it'll send the request through these middleware:

  • EncryptCookies
  • AddQueuedCookiesToResponse
  • StartSession
  • VerifyCsrfToken

This will allow the regular web guard shipped with laravel to work, since it needs access to your session. If the requests are not "stateful", sessions won't be accessible.

All you need to do now is change the authentication guard in your api.php routes file from auth:api to auth:airlock. This guard will check if there's an authentication session available and allow the request to pass. No tokens are stored in your frontend, no tokens are sent with the request, just regular highly secure session-based authentication.

Session Configuration

Airlock also ensures your sessions are stored securely by setting two configuration values:

  • session.http_only: true
  • session.same_site: lax

The first setting ensures that browsers cannot access the session ID stored in your cookies, only your backend can. The second ensures that the cookie will be sent only if the user is on your site; not viewing it via iframe or making an ajax request from a different host, etc...

The Session ID

The client making the request must be able to send the session ID, for that you need to do a couple of things:

  1. Set a proper value for the session.domain configuration of the application running your API. If you set it to .domain.com, all requests coming from that domain or any subdomain will have the session ID and will be able to make the request.
  2. Set the withCredentials property of your HTTP client to true. This will instruct the client to include the cookies in the request. Otherwise it won't be included if the SPA is on a different subdomain.

That's why you can't have the API hosted in domain.com while the SPA is on another-domain.com. They both need to be on the same domain so they get the same session ID.

CSRF Protection

By default, all POST/PATCH/PUT/DELETE requests to your api routes are allowed. However, since Airlock authenticates your users using a session, we need to ensure that these requests are coming from your SPA, not any other 3rd party claiming to be the SPA. Airlock adds the VerifyCsrfToken middleware to accomplish that.

Before authenticating the user, you need to make a GET request to /airlock/csrf-cookie. The response will include the XSRF-TOKEN cookie which will be stored in your browser and used by your HTTP client (e.g. axios) in future requests.

Laravel will read the token attached to the request headers and compare it with the token stored in your session.

CORS

Modern web browsers have security policies in place to protect users from hijacking. If you're visiting domain.com and that site is trying to make a request to another-domain.com, browsers make sure that another-domain.com doesn't mind such request.

If you have your API on api.domain.com and the SPA on spa.domain.com, you need to explicitly allow requests from your SPA to your API since they aren't on the same domain.

You can install fruitcake/laravel-cors to help you with that.

Here's how you may configure it:

return [
 'paths' => [
  'api/*', 
  'login', 
  'airlock/csrf-cookie'
 ],

 'allowed_origins' => [
  'https://spa.domain.com',
  'https://third.party.com'
 ],

 'supports_credentials' => true,
];

The first attribute enables CORS for the specified paths. All CORS rules we set will only be applied to these paths.

Next, we'll allow access to only a set of origins that we trust.

Finally we instruct Laravel to send the Access-Control-Allow-Credentials header in every response, this will make browsers share the cookies sent with the JavaScript app running.

Issuing Tokens

You can only authenticate users using sessions if they are using a javascript application running on the same domain/subdomain as your API. For this reason, Airlock allows you to issue personal access tokens for apps and devices that won't have access to the session.

$user->createToken(
 'laravel-forge', 
 ['server:create', 'server:delete']
);

Using this piece of code, you're creating a token named laravel-forge that has the ability to create and delete servers.

In your API, you can check a token ability using:

$user->tokenCan('server:create');

You can also revoke the token using:

$user->tokens()->whereName('laravel-forge')->delete();

Or revoke the currently used token (log the user out):

auth()->user()->currentAccessToken()->delete();

Tokens are hashed using SHA-256 hashing and stored in a database table. Airlock will check the token sent in an Authorization header and make sure it exists in the database and is still valid. You can configure the token expiration by setting airlock.expiration.

JSON Web Tokens

Tokens generated by Airlock are not JWT. The value you path to the Authorization header is a random string that represents the token key in the database. All details about the token is in the database row, not on on the token itself. This makes it easier to update the token name & abilities by updating the database record.

Passport

You can use Airlock instead of passport if your application doesn't need the Client Credential grant to allow machine-to-machine communication or the Authorization Code grant. These types of communication require more advanced authentication techniques that Airlock is not built to handle.

In all other scenarios, Airlock is a really good option for authenticating your users without having to setup an entire OAuth2 server implementation.

Top comments (4)

Collapse
 
michi profile image
Michael Z

Doesn't samesite: lax already protect against csrf tokens on evergreen browsers?

Collapse
 
themsaid profile image
Mohamed Said

If you are 100% sure all your users are on an evergreen browser then yes. But it's always a good practice to use all the extra security layers to protect your users.

Collapse
 
michi profile image
Michael Z

Yes that's very true.
But what stops the attacker from retrieving a fresh csrf token using the /csrf-cookie endpoint? Are there security measures in place?

Thread Thread
 
themsaid profile image
Mohamed Said

If they gain access to the cookies then yes. The whole point is just adding more layers of security and following all recommendations and best practices.