Cookies not saving when being set from json response - node.js

I am trying to save the access_token and refresh_token of a user after oAuth.
Flow:
User authenticates and I retrieve their accessToken, refreshToken, and user data from the social site
I send data to a remix resource route
In the action for the remix-resource route, I save the data and then try to set the access token and refresh token using 'Set-Cookie' but it doesn't work.
Including the relevant part of my /api/setUser resource route where I try to set the cookie
export let action: ActionFunction = async ({ request }) => {
const session = await sessionStorage.getSession(
request.headers.get('Cookie')
);
const jsonData = await request.json();
session.set('access_token', jsonData['accessToken']);
session.set('refresh_token', jsonData['refreshToken']);
return json<LoaderData>(
{ status: 'ok' },
{
headers: {
'Set-Cookie': await sessionStorage.commitSession(session),
},
}
);
};

Make sure that in your createCookieSessionStorage that you have the cookie path set to / otherwise, the cookie is only visible on the route you set it.

Related

Unable to retrieve a refresh cookie in expressjs (react and nodejs app)

I am unable to retrieve a cookie that I sent earlier.
As part of login, I sent back a refresh token as an httpOnly cookie.
const payload = {name, email};
console.log("payload: ", payload);
const accessToken = jsonwebtoken.sign(payload, process.env.ACCESS_TOKEN_KEY, { expiresIn: '15m' });
const refreshToken = jsonwebtoken.sign(payload, process.env.REFRESH_TOKEN_KEY, { expiresIn: '1d' });
console.log("Access Token:", accessToken); // access token is generated
console.log("Refresh Token:", refreshToken); // refresh token is generated
res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: false, sameSite: 'Lax', maxAge: 24*60*60*1000 }); // call succeeded. what is the name of cookie?
res.json({ accessToken });
Later on a refresh endpoint I look for a cookie and can't find it:
export const handleRefreshToken = async (req, res) => {
console.log("Request Cookies", req.cookies);
const cookies = req.cookies;
if (!cookies?.refreshToken) return res.sendStatus(401);
I see the following cookies:
_ga: 'xxxxxxxxxxxxxxxxx',
_gid: 'xxxxxxxxxxxxxxxx',
_gat_gtag_UA_xxxxxx: 'x',
_ga_QPY49S2WC4: 'xxxxxxxxxxxxxxxxxxx'
This is on my dev environment with nodejs running on localhost:5000.
Update: Using devtools (Network) I see the cookie in the response on the client side. The name of the cookie is 'refreshToken'. However, the cookie doesn't show up on the browser when I look at the cookies on the browser. Perhaps, the cookie isn't being retained on the browser. Any suggestions on why that could be?
Update2: The link provided by #Konrad Linkowski worked. When the axios request is made from the client, I needed the option "{ withCredentials: true }".
The error was on the client end. The express code functioned correctly. This link explains it: Does Axios support Set-Cookie? Is it possible to authenticate through Axios HTTP request?
My original call on the client side (using axios) was:
const res = await axios.post('/login', { ident: email, password });
Instead it should have been:
const res = await axios.post('/login', { ident: email, password }, { withCredentials: true });

How to use jwt token with httpOnly when sending a request to api

I am making an app that allows logins, and when you login it sends a jwt to the cookies.
My app allows people to create tasks, but I cannot do so without the jwt token. The thing is I cannot access the token because httpOnly is set to true.
this is my code:
const task = {
title: document.querySelector(classes.taskTitle).value,
type: document.querySelector(classes.taskType).value,
subject: document.querySelector(classes.taskSubject).value,
date: document.querySelector(classes.taskDate).value,
descriptions,
timeFrame,
};
try {
const res = await axios({
method: "post",
url: "/api/v1/tasks",// I cannot set authorization header to bearer token because document.cookie does not work
data: task,
});
console.log(res.data);
} catch (err) {
console.log(err.response.data);
}

Is there a way to programatically login to using AzureAD with Cypress on PKCE flow?

I want to athenticate myself (React application) using cypress.js (https://www.cypress.io/). Is there a way to do it programatically with PKCE? As i was reading and looking into all examples - all of them are using implicit flow
I was trying to use solutions like https://www.npmjs.com/package/react-adal but with no success as it needs an implicit flow to be turned on
I was trying this as well: https://xebia.com/blog/how-to-use-azure-ad-single-sign-on-with-cypress/ with no success
I expected to programatically signin inside cypress and save user info and access_token to sessionStorage to be able to perform another api calls
I have not found a way to do this programmatically with PKCE per se, but with the MSAL 2.0 library (#azure/msal-browser on npm) I was able to fill in the account cache ahead of time so it thought it was already logged in. The process looks like this:
With a cy.task, send a request to Azure AD using the ROPC flow to get the tokens.
const scopes = [
'openid',
'profile',
'user.read',
'email',
'offline_access' // needed to get a refresh token
];
const formdata = new URLSearchParams({
'grant_type': 'password',
'scope': scopes.join(' '),
'client_info': 1, // returns an extra token that MSAL needs
'client_id': aadClientId,
'client_secret': aadClientSecret,
'username': aadUsername,
'password': aadPassword,
});
const response = await fetch(`https://login.microsoft.com/${aadTenantId}/oauth2/v2.0/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formdata.toString(),
});
const tokens = await response.json();
Transform the tokens into the cache entries that MSAL wants (based on observing it in a real browser)
// The token tells us how many seconds until expiration;
// MSAL wants to know the timestamp of expiration.
const cachedAt = Math.round(new Date().getTime()/1000);
const expiresOn = cachedAt + tokens.expires_in;
const extendedExpiresOn = cachedAt + tokens.ext_expires_in;
// We can pull the rest of the data we need off of the ID token body
const id_token = JSON.parse(Buffer.from(tokens.id_token.split('.')[1], 'base64').toString('utf-8'));
const clientId = id_token.aud;
const tenantId = id_token.tid;
const userId = id_token.oid;
const name = id_token.name;
const username = id_token.preferred_username;
const environment = 'login.windows.net'; // 🤷‍♂️
const homeAccountId = `${userId}.${tenantId}`;
const cacheEntries = {};
// client info
cacheEntries[`${homeAccountId}-${environment}-${tenantId}`] = JSON.stringify({
authorityType: 'MSSTS',
clientInfo: tokens.client_info,
environment,
homeAccountId,
localAccountId: userId,
name,
realm: tenantId,
username,
});
// access token
cacheEntries[`${homeAccountId}-${environment}-accesstoken-${clientId}-${tenantId}-${token.scope}`] = JSON.stringify({
cachedAt: cachedAt.toString(),
clientId,
credentialType: "AccessToken",
environment,
expiresOn: expiresOn.toString(),
extendedExpiresOn: extendedExpiresOn.toString(),
homeAccountId,
realm: tenantId,
secret: tokens.access_token,
target: tokens.scope,
});
// id token
cacheEntries[`${homeAccountId}-${environment}-idtoken-${clientId}-${tenantId}-`] = JSON.stringify({
clientId,
credentialType: "IdToken",
environment,
homeAccountId,
realm: tenantId,
secret: tokens.id_token,
});
// refresh token
cacheEntries[`${homeAccountId}-${environment}-refreshtoken-${clientId}--`] = JSON.stringify({
clientId,
credentialType: "RefreshToken",
environment,
homeAccountId,
secret: tokens.refresh_token,
});
Use cy.window to store those in sessionStorage or localStorage, depending on how you have MSAL configured.
cy.task('login').then(cacheEntries => {
cy.window().then(window => {
for (let entry in cacheEntries) {
window.sessionStorage.setItem(entry, cacheEntries[entry]);
}
});
});
It's super fragile and not very pretty, but it works! The user that Cypress logs in as needs MFA to be disabled, of course.

how can I send token in header authentication Bearer to Browser

that is my code in helper signin router
so I'm trying to set cookie to browser after confirm sign in
exports.signin = (req,res) => {
db.User.findOne({email:req.body.email}).then(user => {
user.comparePassword(req.body.password,function(err,isMatch){
if(isMatch){
let token = jwt.sign({userId: user.id},process.env.SECRET_KEY,{ expiresIn: 60*5 });
res.setHeader() //here I wanna send header Bearer to the browser
} else {
res.status(400).json({message:"Incorrect Password!"});
}
})
})
.catch(err => {
return res.status(404).send('No user found.');
})**strong text**
}
Authorization header is a client header, you cannot set it on the server(Server does not need authorization). You need to send it in a JSON response and then handle it in the client side.
Your server sends a JSON response with the token.
Your client sets the Authorization header and send it to any route that reaquires authorization
javascript client example:
var myHeaders = new Headers()
/**you need to get the token and put it in myToken var.
Where to store the token is the real question, you need
to take care about the storage you choose beacause of
the security risks*/
myHeaders.append('Content-Type','application/json; charset=utf-8');
myHeaders.append('Authorization', 'Bearer ' + myToken);
fetch( '/myurl', {
credentials: 'include',
headers: myHeaders,
method: 'GET'
}).then( res => {
return res.json();
}).then( res => {
/**your stuff*/
});
Then in your server check for the headers and you will see the Bearer
In Node.js res.setHeader() and Express js res.header() is an alias of res.set() method.
you can use in following ways :
res.setHeader('Authorization', 'Bearer '+ token);
res.header('Authorization', 'Bearer '+ token);
But I recommend you to read jwt example (angularjs & node) : https://code.tutsplus.com/tutorials/token-based-authentication-with-angularjs-nodejs--cms-22543

Node/react best practice: How do I keep track on the current client/user in OAuth2 flow?

I'm a beginner with Node and React, and web programming in general. I want to import user credentials from LinkedIn's API and for that I need to authenticate using OAuth2.
My approach so far is to make an API-call from the client side to the LinkedIn oauth API with the relevant parameters, including a redirect URI which leads to an API endpoint on my node server. When the user has been redirected and approved LinkedIn's authentication dialog box, they will be redirected to the node server with an access token.
My question is as follows: I now want to update the user in my database with their corresponding access token, but how do I know which user to update when I can't get any information about the client in my function that handles the last redirect and fetches the access token?
Here's my node function that handles the redirect from LinkedIn:
router.get('/redirect', (req, res) => {
// Handle cancel by user
if(req.query.error){
console.log(req.query.error_description)
return
}
// Extract variables
const code = req.query.code
const state = req.query.state
// Check that state matches
if (state !== testState) {
console.log("State doesnt match")
return
}
// Exchange Authorization Code for an Access Token
var options = {
method: 'POST',
url: 'https://www.linkedin.com/oauth/v2/accessToken',
form: {
client_id: 'theClientID',
client_secret: 'theClienSecret',
grant_type: 'authorization_code',
code: code,
redirect_uri: 'http://localhost:3000/api/linkedin/redirect'
},
headers:
{ 'cache-control': 'no-cache',
"content-type": "application/json",
'user-agent': 'node.js' },
json: true };
// make the actual request
request(options, (error, response, body) => {
if (error) {
res.status(500).json({
message: error
})
return
}
// Extract access token
const token = body.access_token;
// Here I want to save access token to DB with the corresponding
// user, but I don't know which user to update
})
// Redirect user to profile
res.writeHead(301, {
Location: 'http://localhost:3000/profile'
})
res.end()
})
I had a really hard time formulating this question but I hope that my message gets through.

Resources