Solución:
Por lo tanto, si los tokens nunca se usan, Sanctum es básicamente el mismo que el método de autenticación predeterminado, ¿estoy en lo correcto?
Sí, debajo del capó usa la autenticación predeterminada de laravel.
Echando un vistazo a la guardia del santuario (debajo del código tomado de github. Se comprometió por última vez el 11 de abril, santuario 2.x)
<?php
namespace LaravelSanctum;
use IlluminateContractsAuthFactory as AuthFactory;
use IlluminateHttpRequest;
class Guard
{
/**
* The authentication factory implementation.
*
* @var IlluminateContractsAuthFactory
*/
protected $auth;
/**
* The number of minutes tokens should be allowed to remain valid.
*
* @var int
*/
protected $expiration;
/**
* Create a new guard instance.
*
* @param IlluminateContractsAuthFactory $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 IlluminateHttpRequest $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)
));
}
}
Si marca el _invoke()
método,
if ($user = $this->auth->guard(config('sanctum.guard', 'web'))->user()) {
return $this->supportsTokens($user)
? $user->withAccessToken(new TransientToken)
: $user;
}
el usuario autenticado se encuentra usando
$user = $this->auth->guard(config('sanctum.guard', 'web'))->user()
Después de verificar el archivo de configuración de sanctum, no hay sanctum.guard
config actualmente (probablemente esté destinado a alguna versión futura), por lo que sanctum comprueba con el web
guard de forma predeterminada, por lo que básicamente hace lo mismo que sus rutas web predeterminadas.
Pero has entendido mal el uso de Sanctum. Sanctum es para la autenticación de API y no para la autenticación web (aunque también se puede utilizar la autenticación web). La autenticación sin token de Sanctum es para que sus SPA puedan usar la misma API que las aplicaciones móviles (que usan autenticación de token) sin necesidad de tokens y brindando los beneficios de csrf y autenticación basada en sesión.
Para ayudarlo a comprender mejor, suponga que ha creado una API que usa tokens (si ya está usando sanctum para tokens, eso simplifica las cosas) para la autenticación. Ahora desea construir un SPA (que puede construirse dentro del proyecto laravel en sí, o en un proyecto separado, en el mismo dominio o en un dominio diferente) que usará las mismas API, pero como esto lo creará usted, es un sitio confiable, por lo que no desea que use tokens, sino que use la autenticación basada en sesión predeterminada de laravel junto con la protección csrf mientras también usa las mismas rutas api. El SPA se comunicará con el servidor a través de ajax. También desea asegurarse de que solo su SPA pueda usar la autenticación basada en sesiones y no permita que otros sitios de terceros la usen.
Así que aquí es donde entra Sanctum. Solo tendría que agregar el middleware de Sanctum a su api
grupo de ruta en app/Http/Kernel.php
use LaravelSanctumHttpMiddlewareEnsureFrontendRequestsAreStateful;
'api' => [
EnsureFrontendRequestsAreStateful::class,
'throttle:60,1',
IlluminateRoutingMiddlewareSubstituteBindings::class,
],
Luego configure sanctum para permitir el dominio de su SPA y configure cors (consulte los documentos para saber cómo hacer esto). Luego solo agrega el auth:sanctum
middleware a su ruta y habrá terminado con la configuración del lado del servidor.
Ahora, estas rutas autenticarán a los usuarios si la solicitud tiene un token o si tiene estado (cookie de sesión).
Ahora su SPA puede comunicarse con su API sin tokens.
Para obtener protección csrf, llame al csrf-cookie
Solicite primero, esto configurará un token csrf en sus cookies, y axios lo adjuntará automáticamente a las solicitudes posteriores.
axios.get('/sanctum/csrf-cookie').then(response => {
// Login...
})
¿Cuál es la diferencia entre sanctum y pasaporte ya que hacen lo mismo pero se dice que Sanctum es ligero?
Bueno, es como dice, Sanctum es liviano. Esto se debe a que Passport proporciona una funcionalidad completa de Oauth, mientras que Sanctum solo se enfoca en crear y administrar tokens. Para explicar Oauth de una manera simple, debe haber visto esos Sign in with Google
, Sign in with Facebook
, Sign in with Github
en diferentes sitios, y luego puede iniciar sesión en esos sitios usando su cuenta de google / facebook / github. Esto es posible porque Google, Facebook y Github proporcionan la funcionalidad Oauth (solo un ejemplo simple, sin entrar en demasiados detalles). Para la mayoría de los sitios web, realmente no necesita Passport, ya que proporciona muchas funciones que no necesita. Para una autenticación de API simple, Sanctum es más que suficiente
NOTA: Esta respuesta es para Laravel Sanctum + SPA del mismo dominio
Para agregar a estas respuestas, la autenticación predeterminada de Laravel usa la web
guard, por lo que debe usar eso para sus rutas de autenticación (para la aplicación SPA del mismo dominio).
Por ejemplo, puede crear sus propias rutas que apunten a la ruta de Laravel. RegistersUsers
rasgo y AuthenticatesUsers
rasgo:
web.php
Route::group(['middleware' => ['guest', 'throttle:10,5']], function () {
Route::post('register', 'Auth[email protected]')->name('register');
Route::post('login', 'Auth[email protected]')->name('login');
Route::post('password/email', 'Auth[email protected]');
Route::post('password/reset', 'Auth[email protected]');
Route::post('email/verify/{user}', 'Auth[email protected]')->name('verification.verify');
Route::post('email/resend', 'Auth[email protected]');
Route::post('oauth/{driver}', 'Auth[email protected]')->name('oauth.redirect');
Route::get('oauth/{driver}/callback', 'Auth[email protected]')->name('oauth.callback');
});
Route::post('logout', 'Auth[email protected]')->name('logout');
Pero asegúrate de que estén en el web.php
expediente. Por ejemplo, si están en el api.php
archivo, vi algunos errores extraños sobre session store not on request
y RequestGuard::logout()
no es una función. Creo que esto tiene algo que ver con la protección de autenticación predeterminada a través de $this->guard()
en los rasgos de autenticación, y algo que ver con api.php
‘s /api
prefijo.
los /api
prefijo parecía relacionado porque si usa el paquete compositor Ziggy para lograr route('login')
y route('logout')
, en realidad resuelven /api/login
y /api/logout
.
Sospecho que eso causó un problema con Sanctum. La solución fue asegurarse de que las rutas estuvieran en web.php
. Una persona puede reproducir este error si su configuración es similar, o por ejemplo, si tiene Auth::routes()
declarado en api.php
.
Revisa tu Kernel.php (debería ser así):
protected $middlewareGroups = [
'web' => [
AppHttpMiddlewareEncryptCookies::class,
IlluminateCookieMiddlewareAddQueuedCookiesToResponse::class,
IlluminateSessionMiddlewareStartSession::class,
// IlluminateSessionMiddlewareAuthenticateSession::class,
IlluminateViewMiddlewareShareErrorsFromSession::class,
AppHttpMiddlewareVerifyCsrfToken::class,
IlluminateRoutingMiddlewareSubstituteBindings::class,
],
'api' => [
EnsureFrontendRequestsAreStateful::class,
IlluminateRoutingMiddlewareSubstituteBindings::class,
'throttle:60,1',
],
];
Si usted tiene StartSession
en tus api
grupo de middleware, su configuración es incorrecta o innecesariamente complicada.
Aquí está mi trabajo ./config/auth.php archivo para su comparación:
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
...
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
Entonces, puede usar el guest
middleware para rutas de inicio de sesión / registro y, lo que es más importante, debe declarar todos sus puntos finales que sirven JSON en api.php
y usa el auth:sanctum
middleware en esas rutas.
Una vez que crea que lo tiene funcionando, tengo dos pasos de prueba / depuración para usted:
Uno:
- abrir Chrome> panel de herramientas de desarrollo
- ir a la pestaña Aplicaciones
- Verifique para asegurarse de que haya dos cookies:
<app_name>_session
, yXSRF-TOKEN
- con la casilla de verificación recuérdame y
remember: true
en la carga útil de inicio de sesión, asegúrese de que haya una tercera cookie pararemember_web_<hash>
- asegúrese de que la cookie de sesión sea
httpOnly
y asegúrese de que la cookie CSRF no lo esté (para que su JavaScript pueda acceder a ella)
Dos, en sus pruebas unitarias, asegúrese de que después $this->postJson(route('login'), $credentials)
, ves esto:
-
Auth::check()
debería volver verdadero -
Auth::user()
debe devolver el objeto de usuario -
Auth::logout()
debe cerrar la sesión del usuario, e inmediatamente después de eso,$this->assertGuest('web');
debería volver verdadero
No se emocione demasiado hasta que haya verificado esos dos pasos, y emocione una vez que haya verificado esos pasos con éxito. Eso significará que está utilizando la lógica de autenticación predeterminada de Laravel.
Por si acaso, aquí hay un ejemplo de cómo adjuntar el token CSRF a través de 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}.`);
}
});