NextJS api cookies are not set in response header - laravel

I'm using NextJs getInitialProps trying to refresh the access token.
if(!access_token && ctx.req) {
try {
await axios({
method: 'GET',
url: 'http://localhost:3000/api/refresh_token',
withCredentials: true,
headers: refresh_token ? { cookie: ctx.req.headers.cookie } : {}
})
console.log(ctx.res.headers)
} catch (error) {
console.log(error.response)
}
}
and this is the api endpoint:
import axios from 'axios'
import { serialize } from 'cookie'
export default async (req, res) => {
try {
const response = await axios.post('http://localhost/oauth/token', {
grant_type: 'refresh_token',
refresh_token: req.cookies.refresh_token,
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
scope: '*',
})
const { access_token, refresh_token, expires_in } = response.data
res.setHeader('Set-Cookie', [
serialize('access_token', access_token, { path: '/', maxAge: expires_in, httpOnly: true, sameSite: true }),
serialize('refresh_token', refresh_token, { path: '/', httpOnly: true, sameSite: true })
])
return res.status(200).json({ message: 'yess' })
} catch (error) {
res.status(error.status || 500).end(error.response.data.message)
}
}
but when I log the response headers the cookies are not set. Chrome also doesnt show the cookies. Am I missing a step? Maybe because its server side rendering I have to do it another way? I've honestly been stuck with this for 3 days now and I've tried everything (I think so at least) and with no success. Its bugging me as I have no idea to setup proper auth in NextJs.

Related

Nuxtjs Auth module with Laravel Sanctum Provider

I have integrated the nuxt/auth module with the Laravel Sanctum provider and the login works fine, the problem is when I want to remember the user.
I sent to the api a boolean parameter with the name remember and in the api the option remember is assigned well, the token is saved in the user table and the nuxt/auth module sets the cookie remember_web_59ba36addc2b2b2b2f9401580f014c7f58ea4e30989d but if I set the SESSION_LIFETIME to 5 minutes when I refresh the page after 5 minutes the user is disconnected and does not keep the session until 2027 which is the date assigned to the cookie, I attach an image with the dates of the cookies.
nuxt.config.js
auth: {
strategies: {
'laravelSanctum': {
provider: 'laravel/sanctum',
url: process.env.BASE_URL
},
}
},
// Axios module configuration: https://go.nuxtjs.dev/config-axios
axios: {
baseUrl: process.env.BASE_URL,
credentials: true
},
And on the login page
this.$auth.loginWith('laravelSanctum', {
data: this.form
})
The cookies with the times
in nuxt.config.js
axios: {
baseURL: 'http://localhost/twillo-api/api'
},
auth: {
redirect: {
login: '/account/login',
callback: '/account/login',
home: '/'
},
strategies: {
local: {
token: {
property: 'data.accessToken',
required: true,
global: true,
maxAge: 43200,
type: 'Bearer',
name: 'Authorization'
},
user: {
property: 'user',
autoFetch: false
},
endpoints: {
login: { url: '/login', method: 'post' },
},
localStorage: {
prefix: 'auth.'
}
}
},
},
And on the login page
setUniversal will store user record in cookie.
this.$auth.loginWith('local', { data: payload })
.then(response => {
if(response.data.status) {
this.$auth.setUser(response.data.data.userData)
this.$auth.$storage.setUniversal('user', response.data.data.userData)
this.$auth.$storage.setUniversal('loggedIn', true)
}else {
this.invalidCredential= response.data.message
}
}).catch(error => {
this.backendErrors = error.response.data.errors
})

How/Where does nuxtjs set the authorization header

I inherited a web project from another developer who's left the company. It is built with nuxtjs and laravel, neither of which I am strong with.
In the web project, after a user goes to a login screen, types in email and password, and presses save, I can see in my developer tools that the nuxtjs framework sends a {email: "user#email.com", password: "password"} to the laravel api at https://example.project.com/api/login. The laravel api then returns a 200 response with this payload { data: { message: "Login successful.", token: "50|Fs88RPU7HON1LnBskm35O9txG0OnXDiG3x4XWILJ" }}. Everything is great so far!
Immediately after the 200 response above, NuxtJS sends another request to https://example.project.com/api/auth/user in an attempt to get information about this user. Nginx gives a 401 response. My first suspicion for why this happens is because NuxtJS sent an Authorization: Bearer undefined as shown in this screenshot of my browser dev tools
I've seen other situations with Authorization: Bearer [object object].
I want to know how does NuxtJS decide what value to provide to the Authorization http header? This is my current nuxt.config.js
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'Blah',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.png' }],
},
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/axios
'#nuxtjs/axios',
'#nuxtjs/auth'
],
auth: {
redirect: {
login: '/login',
logout: '/',
callback: '/api/login',
home: '/'
},
strategies: {
local: {
endpoints: {
login: { url: '/api/login'},
user: { url: '/api/auth/user'}
},
}
},
localStorage: true
},
proxy: {
'/api': {
target: 'https://example.project.com/',
pathRewrite: { '^/api': '' },
},
},
// Axios module configuration: https://go.nuxtjs.dev/config-axios
axios: {
baseURL: 'https://example.project.com/',
credentials: true,
headers : {
common: {
'Accept' : 'application/json'
}
}
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
}
}
And also, this is the line of code in pages/login.vue that starts the login process:
await this.$auth.loginWith('local', { data: {email:"user#email.com",password:"password"} });
Simply add Authorization header as default header right after authorization request. Assuming that server sends access token in response the code might look like this:
const response = await this.$auth.loginWith('local', {
data: {
email: "user#email.com",
password:"password"
}
})
const { token } = response;
axios.defaults.headers.common = { Authorization: `Bearer ${token}` };
change your strategy as below to set property name of the token being returned in response.
strategies: {
local: {
token: {
property: 'token'
},
endpoints: {
login: { url: '/api/login'},
user: { url: '/api/auth/user'}
},
}
},
It will include the authorization header in all your requests using $axios.
also you might need to set propery of the user which you are returning from backend also.
This is done by a library in the background. #nuxtjs/auth or #nuxtjs/auth-next will set the token depending on your config in nuxt.config.ts.
You can find the token in the local storage in auth._token.<strategy>. To use a Bearer token in the subsequent userinfo request you might need to set:
nuxt.config.ts
auth: {
strategies: {
local: {
token: {
type: 'Bearer',
}
}
}

Authorization failed on a fetch request to the Brawl Stars API

I'm trying to send a get request with fetch API to ask the brawl stars API server. I've created an API KEY associated with my IP address. I've tried everything, but I got a 403 response from the server.
Here is my code :
const url = 'https://api.brawlstars.com/v1/players/...';
const token = '...';
const headers = new Headers({
'Accept': 'application/json',
'Authorization': 'Bearer ' + token
});
const options = {
method: 'GET',
headers: headers,
mode: 'cors',
cache: 'default'
};
fetch(url, options)
.then(response => response.json())
.then(console.log)
.catch(console.error);
In the console there is the message : No 'Access-Control-Allow-Origin' header is present on the requested resource because of cors policy.
When I test the request on Insomnia, it works well !
I had a problem with the Brawlstars API a little while back when I was making a Brawlstars command for my Discord bot. I was able to get the API to work however with the following code.
const playerurl = 'https://api.brawlstars.com/v1/players/';
const getJSON = async url => {
try {
const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: 'Bearer <yourapitoken>',
},
});
if(!response.ok) {throw new Error(response.statusText);}
const data = await response.json();
return data;
}
catch(error) {
return error;
}
};
getJSON(playerurl).then(data => {
console.log(data);
}).catch(error => {
console.error(error);
});
I hope this works for you!

Getting 502 response and 'has been blocked by CORS policy' running a simple fetch request to my lambda function

Building a serverless web app on AWS with the serverless framework, I get a CORS error with a 502 response code authenticating against an AWS Cognito user pool
GET https://URL.amazonaws.com/dev/asset/ID-1178 502
index.html:1 Access to fetch at 'https://URL.amazonaws.com/dev/asset/PO-TIENDA1178' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
index.js:109 Uncaught (in promise) TypeError: Failed to fetch
An almost identical request works for another function.
And here are both ajax requests sent from the frontend:
// working just fine
async function getAllAssets() {
const getAssetsUrl = _config.api.invokeUrl + "/assets"
const response = await fetch(getAssetsUrl, {
headers: {
Authorization: authToken
},
type: "GET",
dataType: 'json',
crossDomain: true
})
}
// not working, throwing the error described above
async function getOneAsset() {
const getAssetsUrl = _config.api.invokeUrl + "/asset/" + "ID-1178"
const response = await fetch(getAssetsUrl, {
headers: {
Authorization: authToken
},
type: "GET",
dataType: 'json',
crossDomain: true
})
}
I run both functions onDocReady in the same window.
Here are the definitions in serverless.yaml:
# WORKS 👌🏽
getAssets:
name: ${self:service}-${self:provider.stage}-get-assets
handler: handler.getAssets
role: InventoryLambdaRole
events:
- http:
path: /assets
method: get
cors: true
authorizer:
arn: arn:aws:cognito-idp:eu-west-1:HARDCODED:ARN
# doesn't work
getAsset:
name: ${self:service}-${self:provider.stage}-get-asset
handler: handler.getAsset
role: InventoryLambdaRole
events:
- http:
path: /asset/{assetId}
method: get
cors: true
authorizer:
arn: arn:aws:cognito-idp:eu-west-1:HARDCODED:ARN
And here goes my function implementations in the handler.js:
// get all assets works fine:
module.exports.getAssets = function(event, context, callback) {
const params = {
TableName : 'Assets',
Select: 'ALL_ATTRIBUTES',
}
const request = documentClient.scan(params, function(err, data) {
if (err) {
console.log("Error", err)
} else {
const itemCount = data.Count
const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
itemCount: itemCount,
assets: data
}),
}
callback(null, response);
}
})
}
// get one asset doesn't work:
module.exports.getAsset = function(event, context, callback) {
const params = {
TableName : 'Assets',
Key: {
AssetId: event.pathParameters.assetId // also tried to just hardcode it like this: 'ID-1178'
}
}
const request = documentClient.get(params, function(err, data) {
if (err) {
console.log("Error", err)
} else {
const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
asset: data
}),
}
callback(null, response);
}
})
Although it's a CORS error, as you can see the origin headers are provided, and I found that in combination with the 502 status it might be something before the CORS, e.g. a problem in the function or with authorization. However, I can't see any problems with them so far.
The serverless function itself works as well when invoke it locally:
npm run sls -- invoke local --function getAsset -p test.json
Do you have any ideas what could be the issue or how to debug it?
Your issue may be as simple as having dynamodb:GetItem. This is a different permission than what listing all (ie query or scan) would be

Request fails after retrieving JWT token

I am using Django JWT with DRF and Vue Js with axios. When a user logs in I retrieve and store the token in local storage which I have verified to work. I then redirect to a new page where I make another request to get data. Every time I redirect to this page I get a 401 not authorized and when I refresh the page it works fine. I checked to make sure the token is stored before making the second request which it is. I attempt to get the data on the redirected page in the created hook. I also create an axios instance to deal with the headers and use a base route and then import that into the files where needed, I am not sure if that has something to do with it. This also only happens when the token has expired and you try to retrieve a new one. Should I be refreshing the token instead of trying to get a new one?
Axios instance
import axios from 'axios'
export default axios.create({
baseURL: `http://127.0.0.1:8000/`,
headers: {
'Content-Type': 'application/json',
Authorization: 'JWT ' + localStorage.getItem('token')
},
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'X-CSRFToken',
withCredentials: true
})
Edit
api.js
import axios from 'axios'
export default axios.create({
baseURL: `http://127.0.0.1:8000/`,
headers: {
'Content-Type': 'application/json',
Authorization: 'JWT ' + localStorage.getItem('token')
},
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'X-CSRFToken',
withCredentials: true
})
AppLogin.vue
Confirm is triggered by clicking a login button
confirm: function() {
API.post("accounts/login/", {
email: this.email,
password: this.password,
})
.then(result => {
console.log(result.data.token);
this.token = result.data.token;
localStorage.setItem("token", this.token);
this.getUserInfo()
})
.catch(error => {
console.log(error);
});
},
getUserInfo: function(){
axios.get("http://127.0.0.1:8000/userinfo/get/", {
headers: {
'Content-Type': 'application/json',
Authorization: 'JWT ' + this.token
}
})
.then(response => {
console.log(response.data.pos);
var pos = response.data.pos;
this.reroute(pos);
})
.catch(error => {
console.log(error);
});
},
reroute(pos) {
if (pos == "owner") {
this.$router.push({ path: "/bizhome" });
} else {
this.$router.push({ path: "/" });
}
}
BizHome.vue
This is the page that login redirects to on success
created: function() {
this.getLocations();
},
methods: {
getLocations() {
API.get("business/")
.then(response => {
this.biz = response.data;
})
.catch(error => {
console.log(error);
});
API.get("locations/")
.then(response => {
this.bizLocations = response.data;
})
.catch(error => {
console.log(error);
});
},
}
solution
Using some advice that I had gotten from YuuwakU below, I added the headers directly to the get calls in the getLocations method to overwrite the axios instance headers. It appeared that the new page loaded before the token was updated in the instance. One drawback to this solution though is that I now have to directly add the headers to all the calls I make. The headers in the instance never to update. I did add API.defaults.headers.common['Authorization'] = 'JWT ' + result.data.token; to the confirm method on successful retrieval of the token which should have updated the token for the instance. This did not work for me, if anyone has any ideas I would be interested in hearing them
edit 2
I did figure out why the axios instance did not update it is because I was trying to get the token from local stroage in api.js and it was overriding it. Now it works but the token is not persistent so this is not ideal as well. I will update if I find a better solution.
Final Update
I finally figured out a good solution. I removed the authorization header from the axios instance in api.js then I removed all the headers from all the axios calls. In the confirm method upon successful login I added this line mentioned previously mentioned API.defaults.headers.common['Authorization'] = 'JWT ' + result.data.token; and also added the token to local storage. I have a verify token method that runs before pages load. In that method before I make the post request to verify the token I added API.defaults.headers.common['Authorization'] = 'JWT ' + localstorage.getItem('token'); . This allows a user to navigate aways the site and come back and still use the token if valid and does not require the headers to be set on every call.
The reason why that is happening is because an axios instance has already been created with an expired token that exists in the localStorage. So you have to make sure that the axios instance is updated with a fresh get of the token after login, otherwise you will end up using the old token until a fresh page reload. Try the following:
import axios from 'axios'
export default axios.create({
baseURL: `http://127.0.0.1:8000/`,
headers: {
'Content-Type': 'application/json',
Authorization() { // Converted to a method, similar concept to a vue data property
return `JWT ${localStorage.getItem('token')}`,
}
},
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'X-CSRFToken',
withCredentials: true
})
OR, you can even use axios interceptors as well to fetch from localStorage with each request:
// Add a request interceptor
axios.interceptors.request.use(config => {
// Do something before request is sent
config.headers = {
'Content-Type': 'application/json',
Authorization: 'JWT ' + localStorage.getItem('token')
}
return config
}, function (error) {
// Do something with request error
return Promise.reject(error);
});

Resources