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.
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.
/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:
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: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.
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:
- 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.
- Set the
withCredentialsproperty 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.
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.
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
$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:
You can also revoke the token using:
Or revoke the currently used token (log the user out):
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.
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)
Doesn't samesite: lax already protect against csrf tokens on evergreen browsers?
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.
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?
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.