Reputation: 328
I have a NextJS Frontend with Next-Auth installed and a Laravel Backend using Sanctum When I try to login using the signIn function of Next-Auth, it gives me this error:
Request failed with status code 419
419 has to do with CSRF token but I am setting the token by calling the sanctum/csrf-cookie route before calling the login method
[...nextauth.js]
CredentialsProvider
({
name: 'Email and Password',
credentials: {
email: {label: "Email", type: "email", placeholder: "Your Email"},
password: {label: "Password", type: "Password"}
},
async authorize({email, password}, req) {
await apiClient.get("/sanctum/csrf-cookie");
const user = await apiClient.post('/customer/login', {
email: email,
password: password,
});
if (user) {
return user
} else {
return null
}
}
})
apiClient.js
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'http://localhost:8000',
withCredentials: true,
});
export default apiClient;
The first time I try to sign in, I get redirected to /api/auth/signIn?csrf=true
and when I try to sign in again I'm redirected to /api/auth/error?error=Request failed with status code 419
I tried accessing the backend login routes using an API call from the client and it worked without any hitches.
Why is it failing for a request between two servers while it works fine when called from the client? I am not entirely grasping why the Next server isn't able to send a request with the csrf header to the Laravel Server. Is the cookie not set by the first call to sanctum/csrf-cookie when it comes to the server? Does CSRF not apply when talking between two server?
What am I missing here? Any help would be appreciated.
Following a comment, I tried explicitly passing passing the cookies following this question - Why are cookies not sent to the server via getServerSideProps in Next.js? but I still get a CSRF token mismatch error.
Upvotes: 7
Views: 9594
Reputation: 79
take an eye of these lines:
apiClient.get
apiClient.post
The baseURL is important, because if you are calling from a Server Component you need to make sure that you can reach the server. In my case, my problem was that I use 2 docker containers (1 for the frontend & 1 for the backend). I solved the problem using 2 axios client: an apiClient and a serverClient. The only difference is the baseURL. axios.tsx:
import axios, { AxiosHeaders } from 'axios';
import { API_USER_ACCESS_CONTROL_SERVER_URL } from './constants';
export const axiosClient = axios.create({
baseURL: API_USER_ACCESS_CONTROL_SERVER_URL,
withCredentials: true,
});
export const axiosServer = axios.create({
baseURL: process.env.NEXTAUTH_URL_INTERNAL,
withCredentials: true,
});
The NEXTAUTH_URL_INTERNAL
(NEXTAUTH_URL_INTERNAL=http://auac-nginx) was the container_name of the nginx defined in the docker-compose.yml
finally in the authorize method:
await axiosServer.get('/sanctum/csrf-cookie');
const cookieStore = cookies();
const cookie = cookieStore.get('XSRF-TOKEN');
console.log('register...', cookie);
const response = await axiosServer.post('/api/v1/auth/authenticate', {
email: credentials?.username,
password: credentials?.password,
},
{
headers: {
'X-XSRF-TOKEN': cookie?.value,
},
});
console.log(response);
where the cookie is from import { cookies } from 'next/headers'
Hope this help!
Upvotes: 0
Reputation: 166
After a few days I found a solution for this which worked for me.
First, we need to understand that the code of [...nextauth.js] is server side, so this is run in Node.js, not in the browser, and so we need to set the cookies manually in all the requests we will do on the server side.
From the Laravel Sanctum Documentation:
If your JavaScript HTTP library does not set the value for you, you will need to manually set the X-XSRF-TOKEN header to match the value of the XSRF-TOKEN cookie that is set by this route
So we need to add the cookies manually to the request. Here's the code:
[...nexthauth.js]
import NextAuth from "next-auth"
import Credentials from 'next-auth/providers/credentials'
import axios from "../../../lib/axios";
//This is for getting the laravel-session cookie and the CSRF cookie
//from any response of Sanctum or API Breeze
//In my case, the cookies returned are always two and I only need this,
//so you can edit for get independent of position and cookies.
const getCookiesFromResponse = (res) => {
let cookies = res.headers['set-cookie'][0].split(';')[0] + '; '
cookies += res.headers['set-cookie'][1].split(';')[0] + '; '
return cookies
}
//This is to get the X-XSRF-TOKEN from any response of Sanctum or API Breeze,
//In my case, the token is always returned first,
//so you can edit for get independent of position
const getXXsrfToken = (res) => {
return decodeURIComponent(res.headers['set-cookie'][0].split(';')[0].replace('XSRF-TOKEN=',''))
}
//This method works to make any request to your Laravel API
//res_cookies are the cookies of the response of last request you do
//obviously res_cookies is null in your first request that is "/sanctum/csrf-cookie"
const makeRequest = async (method='get', url, dataForm = null, res_cookies ) => {
const cookies = res_cookies != null ? getCookiesFromResponse(res_cookies) : null
const res = await axios.request({
method: method,
url: url,
data: dataForm,
headers: {
origin: process.env.NEXTAUTH_URL_INTERNAL, // this is your front-end URL, for example in local -> http://localhost:3000
Cookie: cookies, // set cookie manually on server
"X-XSRF-TOKEN": res_cookies ? getXXsrfToken(res_cookies) : null
},
withCredentials: true,
credentials: true,
})
return res
}
const nextAuthOptions = (req, res) => {
return {
providers: [
Credentials({
name: 'Email and Password',
credentials: {
email: { label: "Email", type: "email", placeholder: "Your Email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
const csrf = await makeRequest('get', '/sanctum/csrf-cookie', null, null)
const user = await makeRequest('post', '/customer/login', credentials, csrf )
if(user) return user
return null
}
})
]
}
}
lib/axios.js
import Axios from 'axios'
const axios = Axios.create({
baseURL: process.env.NEXT_PUBLIC_BACKEND_URL,
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
withCredentials: true,
credentials: true
})
export default axios
Now if you need to send the cookies to the front-side (browser), you can see this answer
Upvotes: 14
Reputation: 543
It's a session based communication. You should get a CSRF Token first from the server.
const csrf = () => axios.get('/sanctum/csrf-cookie')
Then in login or register you have to get it like this before you try to login or register.
const login = async (email, pass) => {
await csrf()
axios
.post('/login', {email: email, password: pass})
.then(() => mutate())
.catch(error => {
if (error.response.status !== 422) throw error
})
}
Upvotes: -1
Reputation: 85
I had this issue and solved it after spending some hours just by replacing this line:
import axios from 'axios';
by this line:
import axios from '@/lib/axios'
Upvotes: -5