I'm not quite sure about what is meant in the Laravel documentation, so I'm asking to be sure.
We have the default authentication of Laravel on one side and Sanctum on the other.
It is stated that Sanctum can either do Tokens or simply implement auth. :
For this feature, Sanctum does not use tokens of any kind. Instead, Sanctum uses Laravel's built-in cookie based session authentication services. This provides the benefits of CSRF protection, session authentication, as well as protects against leakage of the authentication credentials via XSS. Sanctum will only attempt to authenticate using cookies when the incoming request originates from your own SPA frontend (Vue.js).
Therefor if Tokens are nevers used, Sanctum is basically the same as the default Authentication method, am I correct? Basically, does it implement the default authentication and add tokens if needed on top of that? If so, what is the difference between sanctum and passport since they do the same thing but Sanctum is said to be lightweight. What does that actually mean?
Thanks for reading
Introduction. Laravel Sanctum provides a featherweight authentication system for SPAs (single page applications), mobile applications, and simple, token based APIs. Sanctum allows each user of your application to generate multiple API tokens for their account.
Chatty Cathy @vincent15000 Passport is an OAuth server implementation, and used to offer OAuth authorisation for your application. Sanctum is an authentication library for “simpler” token-based authentication for clients that need it (i.e. mobile apps) but also offers cookie-based authentication for SPAs.
Laravel JWT authentication vs. Sanctum offers both session-based and token-based authentication and is good for single-page application (SPA) authentications. Passport uses JWT authentication as standard but also implements full OAuth 2.0 authorization.
By default, Laravel Sanctum token authentication will either completely block access to protected routes or allow it at the expense of not being able to detect if the user is logged in. However, there are valid scenarios whereby a route should be accessible to guests and at the same time allow bearer token authentication.
For this feature, Sanctum does not use tokens of any kind. Instead, Sanctum uses Laravel's built-in cookie based session authentication services. Typically, Sanctum utilizes Laravel's web authentication guard to accomplish this.
Both Sanctum's "SPA Authentication" and Laravel's default authentication use cookies because they're easy to use and browsers offer extra security features you can't get with tokens. If your SPA and API are on different domains you can't use cookies.
It uses an authentication guard when performing the SPA authentication. By default, the web guard is used as per the configuration file. The same guard is used in the default Laravel request authentication as well. If your web app has multiple panels (say, admin, manager, customer, etc.), you'd be using multiple authentication guards.
Therefore if Tokens are never used, Sanctum is basically the same as the default Authentication method, am I correct?
Yes, under the hood it uses laravel's default auth.
Taking a look at the sanctum guard (below code taken fro github. It was last commited on Apr 11, sanctum 2.x)
<?php
namespace Laravel\Sanctum;
use Illuminate\Contracts\Auth\Factory as AuthFactory;
use Illuminate\Http\Request;
class Guard
{
/**
* The authentication factory implementation.
*
* @var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* The number of minutes tokens should be allowed to remain valid.
*
* @var int
*/
protected $expiration;
/**
* Create a new guard instance.
*
* @param \Illuminate\Contracts\Auth\Factory $auth
* @param int $expiration
* @return void
*/
public function __construct(AuthFactory $auth, $expiration = null)
{
$this->auth = $auth;
$this->expiration = $expiration;
}
/**
* Retrieve the authenticated user for the incoming request.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function __invoke(Request $request)
{
if ($user = $this->auth->guard(config('sanctum.guard', 'web'))->user()) {
return $this->supportsTokens($user)
? $user->withAccessToken(new TransientToken)
: $user;
}
if ($token = $request->bearerToken()) {
$model = Sanctum::$personalAccessTokenModel;
$accessToken = $model::findToken($token);
if (! $accessToken ||
($this->expiration &&
$accessToken->created_at->lte(now()->subMinutes($this->expiration)))) {
return;
}
return $this->supportsTokens($accessToken->tokenable) ? $accessToken->tokenable->withAccessToken(
tap($accessToken->forceFill(['last_used_at' => now()]))->save()
) : null;
}
}
/**
* Determine if the tokenable model supports API tokens.
*
* @param mixed $tokenable
* @return bool
*/
protected function supportsTokens($tokenable = null)
{
return $tokenable && in_array(HasApiTokens::class, class_uses_recursive(
get_class($tokenable)
));
}
}
If you check the _invoke()
method,
if ($user = $this->auth->guard(config('sanctum.guard', 'web'))->user()) {
return $this->supportsTokens($user)
? $user->withAccessToken(new TransientToken)
: $user;
}
the authenticated user is found using
$user = $this->auth->guard(config('sanctum.guard', 'web'))->user()
After checking the sanctum config file, there is no sanctum.guard
config currently (it's probably meant for some future version), so sanctum checks with the web
guard by default, so it's basically doing the same thing as your default web routes.
But you've misunderstood the use of Sanctum. Sanctum is for API authentication and not for web auth (though it can be used web auth as well). Sanctum's non-token auth is for your SPA's to be able to use the same API as mobile applications ( which use token authentication ) without needing tokens and providing the benefits of csrf and session based auth.
To help you understand better, suppose you have build an API which uses tokens (if it's already using sanctum for tokens, that makes things simpler) for authentication. Now you wish to build an SPA ( which may be build inside the laravel project itself, or a seperate project, on same domain or on different domain ) which will use the same API's, but since this will be built by you, it is a trusted site so you don't want it to use tokens but instead use laravel's default session based auth along with csrf protection while also using the same api routes. The SPA will communicate with the server through ajax. You also want to ensure that only your SPA is allowed to use session based auth and not allow other third party sites to use it.
So this is where Sanctum comes in. You would just need to add the Sanctum middleware to your api
route group in app/Http/Kernel.php
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
'api' => [
EnsureFrontendRequestsAreStateful::class,
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
Then configure sanctum to allow your SPA's domain and configure cors (check the docs to learn how to do this). Then just add the auth:sanctum
middleware to your route and you're done with the serverside setup.
Now these routes will authenticate users if the request has a token or if it is stateful (session cookie).
Now your SPA can communicate with your API without tokens.
To get csrf protection, call the csrf-cookie
request first, this will set up a csrf token in your cookies, and axios will automatically attach it to subsequent requests
axios.get('/sanctum/csrf-cookie').then(response => {
// Login...
})
What is the difference between sanctum and passport since they do the same thing but Sanctum is said to be lightweight.
Well it's just like it says, sanctum is lightweight. This is because Passport provides full Oauth functionality while Sanctum only focuses on creating and managing tokens. To explain Oauth in a simple way, you must have seen those Sign in with Google
, Sign in with Facebook
, Sign in with Github
on different sites, and you can then sign it to those sites using your google/facebook/github account. This is possible because Google, Facebook and Github provide Oauth functionality (just a simple example, not going in to too much detail). For most websites, you don't really need Passport as it provides a lot features that you don't need. For simple api authentication Sanctum is more than enough
NOTE: This answer is for Laravel Sanctum + same-domain SPA
To add to these answers, the default Laravel auth uses the web
guard, so you must use that for your auth routes (for same-domain SPA app).
For example, you can create your own routes that point to Laravel's RegistersUsers
trait and AuthenticatesUsers
trait:
web.php
Route::group(['middleware' => ['guest', 'throttle:10,5']], function () {
Route::post('register', 'Auth\RegisterController@register')->name('register');
Route::post('login', 'Auth\LoginController@login')->name('login');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
Route::post('password/reset', 'Auth\ResetPasswordController@reset');
Route::post('email/verify/{user}', 'Auth\VerificationController@verify')->name('verification.verify');
Route::post('email/resend', 'Auth\VerificationController@resend');
Route::post('oauth/{driver}', 'Auth\OAuthController@redirectToProvider')->name('oauth.redirect');
Route::get('oauth/{driver}/callback', 'Auth\OAuthController@handleProviderCallback')->name('oauth.callback');
});
Route::post('logout', 'Auth\LoginController@logout')->name('logout');
But make sure they are in the web.php
file. For example if they are in the api.php
file, I saw some weird errors about session store not on request
and RequestGuard::logout()
is not a function. I believe this has something to do with the default auth guard via $this->guard()
in the auth traits, and something to do with api.php
's /api
prefix.
The /api
prefix seemed related because if you use the composer package Ziggy to achieve route('login')
and route('logout')
, they actually resolve to /api/login
and /api/logout
.
I suspect that caused an issue with Sanctum. The fix was to make sure the routes were in web.php
. A person may reproduce this error if their config is similar, or for example, if they have Auth::routes()
declared in api.php
.
Double check your Kernel.php (it should be like this):
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
'throttle:60,1',
],
];
If you have StartSession
in your api
middleware group, your config is incorrect or unnecessarily-convoluted.
Here is my working ./config/auth.php file for your comparison:
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
...
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
Then, you can use the guest
middleware for login/registration routes, and very importantly, you should then declare all your JSON-serving endpoints in api.php
and use the auth:sanctum
middleware on those routes.
Once you think you have it working, I have two test/debug steps for you:
One:
<app_name>_session
, and XSRF-TOKEN
remember: true
in login payload, ensure there is a third cookie for remember_web_<hash>
httpOnly
, and make sure the CSRF cookie is not (so your JavaScript can access it)Two, in your unit tests, ensure that after $this->postJson(route('login'), $credentials)
, you see this:
Auth::check()
should return trueAuth::user()
should return the user objectAuth::logout()
should log the user out, and immediately following that, $this->assertGuest('web');
should return trueDon't get too excited until you verify those two steps, and do get excited once you successfully verify those steps. That will mean you are using Laravel's default auth logic.
For good measure, here is an example of attaching the CSRF token via JavaScript:
import Cookies from 'js-cookie';
axios.interceptors.request.use((request) => {
try {
const csrf = Cookies.get('XSRF-TOKEN');
request.withCredentials = true;
if (csrf) {
request.headers.common['XSRF-TOKEN'] = csrf;
}
return request;
} catch (err) {
throw new Error(`axios# Problem with request during pre-flight phase: ${err}.`);
}
});
Sanctum has two separate authentication systems, there is the cookie based session authentication meant to use for single page applications, where you are sending api requests (ajax, fetch etc.) instead of server side rendering (which reads the session cookie on every page load), sanctum makes it possible to use this cookie (the default authentication) without page reloads.
The second authentication system is token based and is meant to use for mobile applications.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With