DEV Community

Cover image for Laravel Sanctum Explained : SPA Authentication

Laravel Sanctum Explained : SPA Authentication

Nicolus on May 24, 2020

This is going to be a multi-part article about Laravel Sanctum (previously known as "Airlock"), the new Laravel authentication system. I've played ...
Collapse
 
danie1net0 profile image
Daniel Neto

What do I need to do if my API is behind a load-balance, for example? How can I use the statefull authentication?

Collapse
 
nicolus profile image
Nicolus

I think this is a different topic, but being behind a load balancer shouldn't prevent you from using stateful authentication : You just need to make sure that if you servers all have access to the same sessions (by storing them in the database or a shared redis rather than on the filesystem), or if it's not practical (for example because your servers are in different geographical regions) use the cookie driver to store sessions in encrypted cookies.

My current setup is that I have 3 PHP servers running behind an HAProxy Load Balancer, and are all connected to the same Redis Cluster where sessions are stored.

Collapse
 
danie1net0 profile image
Daniel Neto

It really makes sense. Thank you.

Collapse
 
nomikz profile image
nomikz

So if front and back on the different domains, then sanctum is not usable?

Collapse
 
nicolus profile image
Nicolus • Edited

If front and back are on completely different domain, Sanctum is not usable in its Stateful (or "SPA") mode because it relies on sessions and you can't have a session cookie work over different domains.

You could use it in it Stateless (or "API") mode though, which I haven't covered in this article and haven't found time cover yet. It would then work as a mobile app (see description here : laravel.com/docs/7.x/sanctum#issui...) so you'd basically have to make an ajax request to exchange an e-mail and password for a Bearer token, and then pass this token in every subsequent request in the "Authorization" header like so :

Authorization: Bearer TheTokenIJusteGotFromTheApiThroughAnAjaxRequest
Enter fullscreen mode Exit fullscreen mode
Collapse
 
barcodelllllll profile image
lllllllll

Thank you very much for the post, but could you do another one telling how it is done from another domain with Sanctum? I've been dealing with it for two days.

Thread Thread
 
nicolus profile image
Nicolus

No problem, I'll see what I can do over the week-end. It's actually long overdue.

Can you tell me a bit more about what you're trying to do so I can make sure to cover your usecase ? Like do you use a front-end framework ? Is there a particular reason you can't have both on the same domain ?

Thread Thread
 
barcodelllllll profile image
lllllllll • Edited

I made a game portal in a classic Laravel web (front and back together). When i finished it, boss tell me he want to separate back and front. So then, i made the classic web API and some copies of a "Client API" / SPA which have the classic web front and which consumes the resources of the classic web "API".

Everything went well until deploying Sanctum in production. In local it worked perfectly but of course, it used php artisan serve for the API and the API clients, both were localhost. Anyway, I was already using token authentication, so I think I'm on the right track (although I think Passport would have been the best for this...) Thanks

Thread Thread
 
nicolus profile image
Nicolus

Just to be sure we're on the same page : you can separate back and front by still using the same top level domain (for example "gameportal.com" for your front-end and api.gameportal.com for your backend. That's the recommended setup described in this article.

Also what does your "frontend" look like ? Is it an SPA built with something like Angular, Vue or React that makes ajax request to your API ? Or is it another Laravel app that makes requests to your API from the server using Curl or Guzzle ?

Either way, Passport is way overkill for what you're trying to do. According to Taylor Otwell himself, the only valid usecase for Passport is if you want a third party websites to allow their users to connect with their account on your site (Something like "Connect with Facebook" or "Connect with Google", except it would be "Connect with gameportal.com").

Thread Thread
 
barcodelllllll profile image
lllllllll

The SPA is another Laravel app that makes requests to my API from the server using Guzzle (Http::class).

API and SPA's have to be on different domains.

Yes, yes, i know Passport is, after all, to connect third party webs.

Thanks!!!

Thread Thread
 
nicolus profile image
Nicolus

OK... That sounds like a pretty weird and inefficient setup but if those are the requirements ¯\(ツ)

I've tested it out locally and thrown out a guide, I'm not publishing it yet because it's nowhere near as complete as I'd like, but let me know if this helps with your situation : dev.to/nicolus/laravel-sanctum-exp...

I've tested locally on two different domains and it works fine, there's no reason it shouldn't work in production unless you have some other issues like a firewall blocking requests between your apps or a DNS issue.

Thread Thread
 
barcodelllllll profile image
lllllllll

Thanks for the reply as always and sorry for my late answer (girl and kids are guilty)

The post you have shared is just like i did.

Why do you mean with inefficient?? ¬¬ Tell me plz.

May be its about a server config which refuses the login post request, but, before deploying Sancum I was making all the requests to serve the page with the GET method (there was no authentication in those days) and there was no problem, so it is possible that the changes I made in the API have made it always redirect with status 301 request to the POST type login endpoint, it may be that, as I say, it's something about .htaccess, something about CORS, I don't know why the hell it redirects it.

I also had to save the token in session variable XD.

Collapse
 
nomikz profile image
nomikz

Thanks for a quick reply.
So it seems to me that sanctum is just another abstraction for passport which was an abstraction for jwt.

Thread Thread
 
nicolus profile image
Nicolus

Well, the way you use it in Stateless mode is very similar to Passport indeed, but it is definitely not an abstraction for Passport, and it doesn't use JWT etiher.

The token that's generated is just an 80 characters random token that's stored in the database and it doesn't contain any information in itself. The point of Sanctum is that it is much much simpler than Passport (which is a full blown Oauth2 server) and simpler than using JWT tokens (which are not inherently secure).

Collapse
 
emekambah profile image
Emeka Mbah

Nice tutorial.
I have a Vue SPA on windows frontend.mydomain.test/ and Backend laravel API on Ubuntu server backend.mydomain.test/.

I have these in .env file

SESSION_DOMAIN=mydomain.test
SANCTUM_STATEFUL_DOMAINS=frontend.mydomain.test

I have also configured core and Sanctum middleware.

config/cors.php

 'paths' => [
        'api/*',
        'graphql',
        '/login',
        '/logout',
        '/sanctum/csrf-cookie'
    ],
``

On a request attempt to  http://backend.mydomain.test/sanctum/csrf-cookie, I receive the error message below. What am I missing?

Access to XMLHttpRequest at 'backend.mydomain.test/sanctum/csrf...' from origin 'frontend.mydomain.test:8000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.



Collapse
 
nicolus profile image
Nicolus

Hi !

Your session domain should be

SESSION_DOMAIN=.mydomain.test
Enter fullscreen mode Exit fullscreen mode

With a . in front of the domain, so that it can be accessed by both the frontend and the backend.

However I doubt that's what is causing your issue with CORS. Make sure the front-end domain is listed in the 'allowed_origins' part of the cors.php config file (or that it's set to ['*']). The paths looks OK, but just in case you could try to replace them with ['*'] too just to make sure there isn't something funky going on there.

Also make sure you have

'supports_credentials' => true
Enter fullscreen mode Exit fullscreen mode

in cors.php.

If none of that helps, have a look at the 'OPTIONS' request in the developer tools of your browser, and check if it returns successfully and if it has the required headers (Access-Control-Allow-Origin etc.) . Sometimes it looks like CORS is failing when really it's a completely unrelated error that makes your app crash with an 500 error before it could send the correct headers.

Collapse
 
eleftrik profile image
Erik D'Ercole

Nice article! Thanks for your clear explanation.
I think Laravel official documentation is not as clear as you are while depicting the difference between the two modes (stateless and stateful - I mean, applied to Sanctum).
In my case, I have a SPA built with Angular (example.com) and a Laravel + Sanctum API (api.example.com). But, in the future, there could be another Vue/Angular frontend on a completely different domain, so I think for me it's better to stick with the stateless authentication (as I always did with Passport).
In your opinion, why should I use stateful authentication (when using a subdomain)? CSRF cookie apart, is there any advantage?
Thank you!

Collapse
 
lalitrocks profile image
lalitrocks

hi gentlemen ....good answer.... but can we use stateeful authentication(cookie based sanctum crsf token auth) for mobile to protect xss attack on mobile or there is only one option for mobile i.e . token based (sanctum stateless auth bearer token )..... awiting for response and also please provide link here for mobile auth article which you are telling to post here
......so many thanks .......

Collapse
 
hungnv234 profile image
Hung Nguyen Van

Thanks for sharing.
In my case, I have 2 SPA: app.mydomain.com and cms.mydomain.com.
Laravel API is: api.mydomain.com and I use sanctum too.

When I login to cms.mydomain.com, the browser has set cookie success and I login success. But when I access app.mydomain.com, browser get same cookies of cms.mydomain.com and I can't login, the request login return status 302 found.

Do you have any idea for me? Thanks

Collapse
 
javierpomachagua profile image
Javier Pomachagua

Hi! I have api.example.com (laravel backend) and app.example.com (nuxt client). I can get successful the cookie but when I login it shows me "Unauthenticated". How do you put your .env?

Collapse
 
_devlim profile image
I'M DEVLIM ⛱️ • Edited

How do you guys test this in localhost environment? And I mean the url localhost:PORT_NUMBER?

I facing various issues when using docker compose, Laravel 8, and nuxt.js(separate repo) 🤣

Collapse
 
cryptoristic profile image
Cryptoristic • Edited

thanks for this tutorial,.

It is possible I have 2 SPA, 1 is my landing page where it will pull data from the backend thru API and another is the Admin portal where user can manage the information. All these 2 SPA shared 1 backend API. How to configure this one? Can I put 2 more than 1 values in SANCTUM_STATEFUL_DOMAINS. TIA

Collapse
 
laugre profile image
Laurent Garnier

Hi there, thx for these explanations, useful to understand better sanctum.
I'm using react as a spa front and sanctum for authentication.
I'm wondering how to manage session lifetime when using sanctum.
Do we have to use 'expiration' preset in sanctum config ?
...or 'lifetime' preset in session config is sufficient ?
and so what 'expiration' preset is about to do ?

Collapse
 
evnikhil profile image
EVNIKHIL

Hi I dint got you. the COOKIES will be set on the header after we Auth::attempt() and can use middle ware sanctum then to authenticate?

Collapse
 
leob profile image
leob

Super, more of these kind of in-depth articles, instead of the "listicle" dross that seems to dominate dev.to these days ...

Collapse
 
suro11777 profile image
S.T. • Edited

SESSION_DOMAIN=.mydomain.local
SANCTUM_STATEFUL_DOMAINS=api.mydomain.local

'supports_credentials' => true

cookies are not saved