Closed hbsmarku closed 9 months ago
I'm having a similar issue, but for me it authenticates properly but when I call /api/user
I get a 401 error
I'm having a similar issue, but for me it authenticates properly but when I call
/api/user
I get a 401 error
May I ask how did you manage to authenticate the user? Did you also use the login function of useSanctumAuth composable and do we have the same frontend and backend setup?
As for the 401 error, have you tried adding the domain of your frontend in the SANCTUM_STATEFUL_DOMAINS env variable in your laravel backend? Hope it helps.
@hbsmarku Yes, I did all that, the only thing I didn't do it install breeze because I only need sanctum, but I did all the necessary modifications:
This only happens on the server, in local it works without issue
Hey @hbsmarku @KiritoXD01
Usually this type of error is related to backend configuration on Laravel side, but to make sure that it's properly configured, could you send the following information?
I'll try to check it out today and get back to you, in the meantime I would suggest double-checking this link - Nuxt Auth Sanctum Docs for Laravel
In the meantime, you can check closed issues as well, there might be something useful for your cases
for me it authenticates properly but when I call
/api/user
I get a 401 error
Do you also use CSR mode or SSR?
@manchenkoff here are is my Nuxt config
sanctum: {
baseUrl: "http://localhost:8000",
userStateKey: "sanctum.user.identity",
redirectIfAuthenticated: true,
origin: process.env.NUXT_PUBLIC_SANCTUM_BASE_URL,
endpoints: {
csrf: "/sanctum/csrf-cookie",
login: "/login",
logout: "/api/logout",
user: "/api/user",
},
redirect: {
keepRequestedRoute: false,
onLogin: "/",
onLogout: "/auth/login",
onAuthOnly: "/auth/login",
onGuestOnly: "/auth/login",
},
},
For the response headers I'll use the picture in the first comment as an example because I had to make changes
@KiritoXD01 but in your nuxt config you have your localhost as a baseUrl, are you trying to access remote production from the local machine?
I asked about Request / Response details for both calls on purpose in order to check if CSRF token returned in the first request as cookie has been sent as a header in the second request, screenshot from the first message is a bit useless to confirm that
In local by default I have like this but on the server I use the env NUXT_PUBLIC_SANCTUM_BASE_URL
and is set to the server URL, let me turn on the server to send you the request
- Nuxt module config (sanctum)
- Request / Response details for both calls from Nuxt (csrf retrieval, login request)
- Laravel config details (sanctum.php, cors.php, session.php, Http/Kernel.php) with corresponding values in .env
Please attach all the information mentioned in my previous message, w/o it I won't be able to help much
@manchenkoff here is everything
Nuxt Config:
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
ssr: false,
modules: [
"@nuxtjs/tailwindcss",
"nuxt-icon",
"nuxt-auth-sanctum",
"@pinia/nuxt",
],
sanctum: {
baseUrl: "http://localhost:8000",
userStateKey: "sanctum.user.identity",
redirectIfAuthenticated: true,
origin: process.env.NUXT_PUBLIC_SANCTUM_BASE_URL,
endpoints: {
csrf: "/sanctum/csrf-cookie",
login: "/login",
logout: "/api/logout",
user: "/api/user",
},
redirect: {
keepRequestedRoute: false,
onLogin: "/",
onLogout: "/auth/login",
onAuthOnly: "/auth/login",
onGuestOnly: "/auth/login",
},
},
});
Request/response Request List
Sanctum CSRF response
Login Response:
User Response:
Cors.php
[
'paths' => ['*'],
'allowed_methods' => ['*'],
'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
]
Http/Kernel.php
here are the keys
@manchenkoff even when I have the baseUrl as localhost, in the server when I set the env it updates it and sends the right API domain
@KiritoXD01 thx for attachments, I'll check it today, meanwhile have you tried to set up the Laravel session domain with a dot at the beginning - .portalpass.com.do
?
@manchenkoff Yes, and also without it
@manchenkoff could Docker be a reason for it to fail? just thinking about it
@KiritoXD01 no, I also use docker for everything, works fine, but the tricky part is always related to Laravel domains, for example you cannot just use domain in SANCTUM_STATEFUL_DOMAINS, you have to pass all urls like "frontend.website.com" and register ".website.com" as SESSION_DOMAIN, also same full url should be in Origin header in each request from Nuxt
What about the SESSION_SECURE_COOKIE?, I have as false for now
@manchenkoff it worked now, it was not the library at the end, The backend was not getting the env properly when I debugged XD, it works perfectly now
Hey @hbsmarku @KiritoXD01
Usually this type of error is related to backend configuration on Laravel side, but to make sure that it's properly configured, could you send the following information?
- Nuxt module config (sanctum)
- Request / Response details for both calls from Nuxt (csrf retrieval, login request)
- Laravel config details (sanctum.php, cors.php, session.php, Http/Kernel.php) with corresponding values in .env
I'll try to check it out today and get back to you, in the meantime I would suggest double-checking this link - Nuxt Auth Sanctum Docs for Laravel
In the meantime, you can check closed issues as well, there might be something useful for your cases
@manchenkoff here is my full setup:
// https://nuxt.com/docs/api/configuration/nuxt-config
let development = process.env.NODE_ENV !== 'production'
export default defineNuxtConfig({
modules: ["nuxt-auth-sanctum", "nuxt-quasar-ui", "@nuxt/image"],
devtools: {
enabled: true,
timeline: {
enabled: true,
},
},
ssr: false,
sanctum: {
origin: development ? "http://127.0.0.1:3000" : undefined, // Your Nuxt app
baseUrl: development ? "http://127.0.0.1:8000" : "https://xx-project-api.xx.com.ph", // Your Laravel API
redirect: {
onLogin: "/dashboard",
onLogout: "/login",
},
endpoints: {
csrf: '/sanctum/csrf-cookie',
login: '/login',
logout: '/logout',
user: '/api/user',
},
csrf: {
cookie: 'XSRF-TOKEN',
header: 'X-XSRF-TOKEN',
},
},
});
<?php
use Laravel\Sanctum\Sanctum;
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort()
))),
/*
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
*/
'guard' => ['web'],
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. This will override any values set in the token's
| "expires_at" attribute, but first-party sessions are not affected.
|
*/
'expiration' => 1,
/*
|--------------------------------------------------------------------------
| Token Prefix
|--------------------------------------------------------------------------
|
| Sanctum can prefix new tokens in order to take advantage of numerous
| security scanning initiatives maintained by open source platforms
| that notify developers if they commit tokens into repositories.
|
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
*/
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
],
];
<?php
$allowed_origins = env('APP_ENV') == 'development' ? ['*'] : ['*.xx.com.ph'];
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie', '*'],
'allowed_methods' => ['*'],
'allowed_origins' => $allowed_origins,
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option controls the default session "driver" that will be used on
| requests. By default, we will use the lightweight native driver but
| you may specify any of the other wonderful drivers provided here.
|
| Supported: "file", "cookie", "database", "apc",
| "memcached", "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'cookie'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to immediately expire on the browser closing, set that option.
|
*/
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false,
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it is stored. All encryption will be run
| automatically by Laravel and you can use the Session like normal.
|
*/
'encrypt' => false,
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When using the native session driver, we need a location where session
| files may be stored. A default has been set for you but a different
| location may be specified. This is only needed for file sessions.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table we
| should use to manage the sessions. Of course, a sensible default is
| provided for you; however, you are free to change this as needed.
|
*/
'table' => 'sessions',
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| While using one of the framework's cache driven session backends you may
| list a cache store that should be used for these sessions. This value
| must match with one of the application's configured cache "stores".
|
| Affects: "apc", "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the cookie used to identify a session
| instance by ID. The name specified here will get used every time a
| new session cookie is created by the framework for every driver.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application but you are free to change this when necessary.
|
*/
'path' => '/',
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| Here you may change the domain of the cookie used to identify a session
| in your application. This will determine which domains the cookie is
| available to in your application. A sensible default has been set.
|
*/
'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. You are free to modify this option if needed.
|
*/
'http_only' => true,
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" since this is a secure default value.
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => 'lax',
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => false,
];
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':web',
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's middleware aliases.
*
* Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
*
* @var array<string, class-string|string>
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}
SANCTUM_STATEFUL_DOMAINS="*xx.com.ph"
SESSION_DOMAIN="xx.com.ph"
Hey @hbsmarku, thanks for the details sharing, here are some recommendations, please apply and let me know if it helps or not!
origin: development ? "http://127.0.0.1:3000" : undefined, // Your Nuxt app
You can remove this line completely if you use >= 0.0.16
SANCTUM_STATEFUL_DOMAINS="*xx.com.ph"
Replace pattern matching with the full URL here like my-project-frontend.xx.com.ph
SESSION_DOMAIN="xx.com.ph"
Session domain must start with dot before TLD when you use subdomains - .xx.com.ph
$allowed_origins = env('APP_ENV') == 'development' ? ['*'] : ['*.xx.com.ph'];
Here you also should pass all available URLs like my-project-frontend.xx.com.ph
instead of *.xx.com.ph
. I personally use same list of domains like in SANCTUM_STATEFUL_DOMAINS.
'paths' => ['api/*', 'sanctum/csrf-cookie', '*'],
Since you have '*'
as the last option, there is no need in keeping 'api/*'
and 'sanctum/csrf-cookie'
'middleware' => [ 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, ],
No need to use authenticate_session
middleware here
@manchenkoff Thank you, I have tried your recommendations. Unfortunately, I'm still facing the same response error during login. I've also noticed that there is still no X-XSRF-TOKEN in the headers of the request.
How to fix this issues?
@manchenkoff Thank you, I have tried your recommendations. Unfortunately, I'm still facing the same response error during login. I've also noticed that there is still no X-XSRF-TOKEN in the headers of the request.
Just to also add, I have checked how the login function works and saw that it uses the useCookie composable of nuxt. I tried to output the XSRF-TOKEN value and its value is undefined even if there is a cookie value set in the cookies tab. I think it is the cause as to why there is no X-XSRF-TOKEN set in the request headers. This problem only happens during production as when I tried to run it on my local environment it properly outputs the cookie value from the useCookie composable.
I tried to output the XSRF-TOKEN value and its value is undefined even if there is a cookie value set in the cookies tab.
Yes, it happens only when you have different domains on your backend and it's not the same as caller host (origin). You can also take a look at similar problem in #13, but that one was related to Homestead environment.
I would suggest debugging your Laravel app to make sure that all environments are set properly and have the same values that you pass from Nuxt app on csrf-cookie request. Might be config cache as well.
I tried to output the XSRF-TOKEN value and its value is undefined even if there is a cookie value set in the cookies tab.
Yes, it happens only when you have different domains on your backend and it's not the same as caller host (origin). You can also take a look at similar problem in #13, but that one was related to Homestead environment.
I would suggest debugging your Laravel app to make sure that all environments are set properly and have the same values that you pass from Nuxt app on csrf-cookie request. Might be config cache as well.
@manchenkoff hello, thank you again for the response. I have already read and tried to implement the solutions mentioned in #13 and other related issues. Still facing the same issue in production.
The csrf cookie endpoint properly returns the cookies. The problem seems to be always the missing X-XSRF-TOKEN header during login. The usecookie composable cannot get the cookie value of XSRF-TOKEN, even if the cookie exists in the cookies tab. I am also clearing my laravel's backend config cache for every changes.
I think the problem lies within the nuxt project. Does deploying my nuxt site as a static and csr-mode related with this issue? Or just like what you've said that I cannot get the cookie value since the domain of the cookie is set as api.xx.com.ph and not frontend.xx.com.ph? If yes, how can I configure my app so that the usecookie composable properly gets the cookie value set by laravel? I hope there is a solution or workaround with this issue, thank you.
I think the problem lies within the nuxt project. Does deploying my nuxt site as a static and csr-mode related with this issue? Or just like what you've said that I cannot get the cookie value since the domain of the cookie is set as api.xx.com.ph and not frontend.xx.com.ph?
Your cookie have to be set on .xx.com.ph
instead of api.xx.com.ph
, and this is SESSION_DOMAIN
setting on Laravel side, since it is not the same TLD - Nuxt has no access to those cookies.
For example, I use this configuration:
frontstore.website.com
backoffice.website.com
api.website.com
I registered both domains SANCTUM_STATEFUL_DOMAINS=frontstore.website.com,backoffice.website.com
and set SESSION_DOMAIN=.website.com
, after that I can work with the same session in both applications.
I think the problem lies within the nuxt project. Does deploying my nuxt site as a static and csr-mode related with this issue? Or just like what you've said that I cannot get the cookie value since the domain of the cookie is set as api.xx.com.ph and not frontend.xx.com.ph?
Your cookie have to be set on
.xx.com.ph
instead ofapi.xx.com.ph
, and this isSESSION_DOMAIN
setting on Laravel side, since it is not the same TLD - Nuxt has no access to those cookies.For example, I use this configuration:
- Frontend Nuxt app at
frontstore.website.com
- Backoffice Nuxt app at
backoffice.website.com
- API Laravel app at
api.website.com
I registered both domains
SANCTUM_STATEFUL_DOMAINS=frontstore.website.com,backoffice.website.com
and setSESSION_DOMAIN=.website.com
, after that I can work with the same session in both applications.
Sadly I've already tried that configuration (SESSION_DOMAIN=.xx.com.ph), and the cookie domain name is still is api.xx.com.ph and is still inaccessible by the useCookie composable.
I've already tried that configuration (SESSION_DOMAIN=.xx.com.ph), and the cookie domain name is still is api.xx.com.ph
and this is a problem you should find a way to fix, it's irrelevant to this module, so unfortunately there is not much I can help with. You can try to find some Laravel related topics like this one - Auth session not working across subdomains
I've already tried that configuration (SESSION_DOMAIN=.xx.com.ph), and the cookie domain name is still is api.xx.com.ph
and this is a problem you should find a way to fix, it's irrelevant to this module, so unfortunately there is not much I can help with. You can try to find some Laravel related topics like this one - Auth session not working across subdomains
Thank you for your insights, the problem is indeed on the backend-laravel side of my application. My hosted environment seems to cache the old environment. It properly works now after clearing the cache, thank you! I am now closing this issue.
You're welcome @hbsmarku, glad it helped! 🙂
It works fine during development but during production the login function from the useSanctumAuth composable does not include the X-XSRF-TOKEN token in the request headers.
Here is my setup:
Nuxt SPA:
Laravel Backend:
Network tab Request/Response: