Access cookie data outside a component - node.js

I'm building a Next.js app using Strapi as a CMS and authentication server. I've managed to get a JWT from the auth endpoint, and have stored it in a cookie. To retrieve secured content from strapi I need to send this JWT in the header
I'm using Apollo to send graphQL queries to strapi, according to their documentation, I can easily set auth headers by adding this to utils/apollo.js
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = cookies.get('jwt') <-- This is what I'd like to do, but I can't figure out how to access cookies at this point.
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
How can I access cookies in the app mentioned above. It's not part of a component, though it is used to build a HOC?
While using localstorage is an option, I've read that cookies are a safer way to store data.
Using cookies with secure isn't an option - I'm not sure if strapi allows it, and also because there are multiple frontend origins vercel builds previews for all branches

Once you set the cookie it will be passed in the Cookie header so you need to retrieve it from there, not the cookies object which is only available in the browser but not available in server-side requests. In addition, if you're using CORS you will need to enable credentials both in the client and server, specifically in the client you need to set credentials: 'include'. This is required setting for cookies to work.

Related

Set-Cookie header is not sent from azure web app

I have a frontend that talks to a backend.
When logging in, the backend creates a cookie and returns it to the client.
When logging out, i want the backend to delete the cookie.
As you can see, i did not set any of the attributes below.
This is the request that is sent from the front.
const response = await axios.get(
`${backendAddress}/api/v1/auth/logout`,
{
withCredentials: true,
credentials: "include",
}
);
This is what I do in the backend
res.setHeader('Set-Cookie', `user-data=; Max-Age=0; path=/; domain=${BACKEND_DOMAIN}`);
res.end();
and these are the cors configuration:
app.use(cors({ origin: CLIENT_URL, credentials: true }));
Locally, this works, I see the Set-Cookie header in the response and I can see that the browser deletes the cookie.
On azure, I run the backend as a web app in azure, and the frontend as a static web app, and connect both of them using the api feature of the static web app.
When i check what headers i receive on the same request that work locally, I can see that the Set-Cookie header is missing.
When looking at the request that is sent to the webapp (the backend), I do see that the cookies are sent with it. And that the cors headers in the response include the right origin and credential headers. I also made sure that the versions of both front and backend are up to date.
Any idea what might cause the Set-Cookie to "disappear" on its way to the browser?
Thanks!

API-client authentications with JWT — Node/TypeScript

I have an API and a web client developed using node and TypeScript. Users can authenticate using JWT. The system works, but I would like to know if it is secure, or if it has flaws:
The system is as follows:
Client POST to /login endpoint on API
On API, when POST /login request is received, a JWT token is generated using some user data as content, and a secret string that is stored as an environment variable:
// SECRET is an environment variable == "bd0b2760-5869-11ec-bf63-0242ac130002"
const userData = {
id: 1,
name: "John Doe",
};
const token = jwt.sign(JSON.stringify(userData), SECRET);
In the response for POST /login API sends two cookies: one holding the token, and another one with raw user data:
return res
.cookie('sessionData', userData, {
httpOnly: true,
path: '/',
domain: "example.com",
})
.cookie('sessionToken', token, {
httpOnly: true,
path: '/',
domain: "example.com",
}).send();
The client receives the token. The client can be sure that sessionToken is valid, as it was sent by the API. It wont verify it, as to do it the SECRET is needed, and we don't want to expose it to the client.
On reload, client will use the sessionData cookie to know that user is logged in, using this data to load the client-side user data.
As those cookies are http cookies, both cookies are attached to every request send to the API, and are received by the API. On every request to endpoints requiring auth, the API will decrypt sessionToken and match it against sessionToken cookie: if they doesnt match, API will delete cookies in the response, effectively logging out the client.
// SECRET is an environment variable == "bd0b2760-5869-11ec-bf63-0242ac130002"
const sessionToken = req.cookies.sessionToken;
const sessionData = req.cookies.sessionData;
const decodedToken = jwt.verify(sessionToken, SECRET);
if(decodedToken.id !== sessionData.id || decodedToken.name !== sessionData.name ) {
return res
.clearCookie('sessionToken', { path: '/', domain: "example.com" })
.clearCookie('sessionData', { path: '/', domain: "example.com" })
}
As previously said, this system works, and it seems secure. But maybe I'm missing something, so better ask. All code is kinda pseudocode.
Any help will be welcome!
It wont verify it, as to do it the SECRET is needed, and we don't want to expose it to the client.
If you want the client to be able to verify this JWT you can use asymmetric signing. Then the client uses a public key, which can be used only to verify that the JWT is OK. It can't be used to sign new JWTs.
On reload, client will use the sessionData cookie to know that user is logged in, using this data to load the client-side user data.
You're using http only cookies so that won't work. Your client is not able to read that cookie.
the API will decrypt sessionToken
Minor thing, but worth remembering - the API will decode the sessionToken cookie, not decrypt. Signed JWTs are encoded, not encrypted. You can create encrypted JWTs (JWE), in which case no one, apart from the API will be able to read the content of the token, but JWEs are much harder to maintain.
the API will decrypt sessionToken and match it against sessionData cookie
I don't see how this gives you any additional security. The session token is a signed JWT, so you can easily verify it to check whether it's been tampered with. If someone managed to steal the sessionToken cookie they might as well have stolen the sessionData cookie and use them together. You're using http only cookies so XSS won't be able to steal the contents of those cookies. You could also use secure flag to make sure that those cookies are not sent through unencrypted connections.

Next.js Authentication with JWT

I am moving a project from React to Next.js and was wondering if the same authentication process is okay. Basically, the user enters their username and password and this is checked against database credentials via an API (Node.js/Express). So, I am not using Next.js internal api functionality, but a totally decoupled API from my Next.js project.
If the login credentials are correct, a JWT token is sent back to the client. I wanted to store that in local storage and then redirect the user. Any future HTTP requests will send the token in the header and check it is valid via the API. Is this okay to do? I ask because I see a lot of Next.js auth using cookies or sessions and don't know if that is the 'standard' approach which I should rather adopt.
My answer is purely based on my experiences and things I read. Feel free to correct it if I happened to be wrong.
So, my way is to store your token in HttpOnly cookie, and always use that cookie to authorize your requests to the Node API via Authorization header. I happen to also use Node.js API in my own project, so I know what's going on.
Following is an example of how I usually handle authentication with Next.js and Node.js API.
In order to ease up authentication problems, I'm using Next.js's built in getServerSideProps function in a page to build a new reusable higher order component that will take care of authentication. In this case, I will name it isLoggedIn.
// isLoggedIn.jsx
export default (GetServerSidePropsFunction) => async (ctx) => {
// 1. Check if there is a token in cookies. Let's assume that your JWT is stored in 'jwt'.
const token = ctx.req.cookies?.jwt || null;
// 2. Perform an authorized HTTP GET request to the private API to check if the user is genuine.
const { data } = await authenticate(...); // your code here...
// 3. If there is no user, or the user is not authenticated, then redirect to homepage.
if (!data) {
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
// 4. Return your usual 'GetServerSideProps' function.
return await GetServerSidePropsFunction(ctx);
};
getServerSideProps will block rendering until the function has been resolved, so make sure your authentication is fast and does not waste much time.
You can use the higher order component like this. Let's call this one profile.jsx, for one's profile page.
// profile.jsx
export default isLoggedIn(async (ctx) => {
// In this component, do anything with the authorized user. Maybe getting his data?
const token = ctx.req.cookies.jwt;
const { data } = await getUserData(...); // don't forget to pass his token in 'Authorization' header.
return {
props: {
data,
},
},
});
This should be secure, as it is almost impossible to manipulate anything that's on server-side, unless one manages to find a way to breach into your back-end.
If you want to make a POST request, then I usually do it like this.
// profile.jsx
const handleEditProfile = async (e) => {
const apiResponse = await axios.post(API_URL, data, { withCredentials: true });
// do anything...
};
In a POST request, the HttpOnly cookie will also be sent to the server, because of the withCredentials parameter being set to true.
There is also an alternative way of using Next.js's serverless API to send the data to the server. Instead of making a POST request to the API, you'll make a POST request to the 'proxy' Next.js's serverless API, where it will perform another POST request to your API.
there is no standard approach. You should be worried about security. I read this blog post: https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/
This is a long but an awesome blog post. everyhing I post here will be quoted from there:
If a JWT is stolen, then the thief can keep using the JWT. An API
that accepts JWTs does an independent verification without depending
on the JWT source so the API server has no way of knowing if this was
a stolen token! This is why JWTs have an expiry value. And these
values are kept short. Common practice is to keep it around 15
minutes.
When server sends you the token, you have to store the JWT on the client persistently.
Doing so you make your app vulnerable to CSRF & XSS attacks, by
malicious forms or scripts to use or steal your token. We need to save our JWT token somewhere so that we can forward it to our API as a header. You might be tempted to persist it in localstorage; don’t do it! This is prone to XSS attacks.
What about saving it in a cookie?
Creating cookies on the client to save the JWT will also be prone to XSS. If it can be read on the client from Javascript outside of
your app - it can be stolen. You might think an HttpOnly cookie
(created by the server instead of the client) will help, but cookies
are vulnerable to CSRF attacks. It is important to note that HttpOnly
and sensible CORS policies cannot prevent CSRF form-submit attacks and
using cookies requires a proper CSRF mitigation strategy.
Note that a SameSite cookie will make Cookie based approaches safe
from CSRF attacks. It might not be a solution if your Auth and API
servers are hosted on different domains, but it should work really
well otherwise!
Where do we save it then?
The OWASP JWT Cheatsheet and OWASP ASVS (Application Security
Verification Standard) prescribe guidelines for handling and storing
tokens.
The sections that are relevant to this are the Token Storage on Client
Side and Token Sidejacking issues in the JWT Cheatsheet, and chapters
3 (Session Management) and 8 (Data Protection) of ASVS.
From the Cheatsheet, "Issue: Token Storage on the Client Side":
Automatically sent by the browser (Cookie storage).
Retrieved even if the browser is restarted (Use of browser localStorage container).
Retrieved in case of XSS issue (Cookie accessible to JavaScript code or Token stored in browser local/session storage).
"How to Prevent:"
Store the token using the browser sessionStorage container.
Add it as a Bearer HTTP Authentication header with JavaScript when calling services.
Add fingerprint information to the token.
By storing the token in browser sessionStorage container it exposes
the token to being stolen through a XSS attack. However, fingerprints
added to the token prevent reuse of the stolen token by the attacker
on their machine. To close a maximum of exploitation surfaces for an
attacker, add a browser Content Security Policy to harden the
execution context.
"FingerPrint"
Where a fingerprint is the implementation of the following guidelines
from the Token Sidejacking issue: This attack occurs when a token has been intercepted/stolen by an attacker and they use it to gain access to the system using targeted user identity.
"How to Prevent":
A way to prevent it is to add a "user context" in the token. A user context will be composed of the following information:
A random string will be generated during the authentication phase. It will be sent to the client as a hardened cookie (flags: HttpOnly +
Secure + SameSite + cookie prefixes).
A SHA256 hash of the random string will be stored in the token (instead of the raw value) in order to prevent any XSS issues allowing
the attacker to read the random string value and setting the expected
cookie.

authentication header vs query parameter in google cloud endpoints

I have tried everything, yet I cannot access my API using google cloud endpoints using a Authentication:Bearer header. According to Cloud Endpoints Docs:
When you send a request using an authentication token, for security reasons, we recommend that you put the token in the Authorization:Bearer header.
it also says:
If you cannot use the header when sending the request, you can put the authentication token in a query parameter called access_token.
I can perfectly access the API using access_token=" +idToken in my URL. However, when I try to send an HTTP request with the Authentication header like this:
const url =
"https://<PROJECTNAME>.appspot.com/getbalance";
axios
.get(url,{headers:{'Authentication':'Bearer '+idToken}})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
I get this error:
JWT validation failed: Missing or invalid credentials
Is sending the token in a query parameter as safe as sending it in the header?
Your code example shows you setting an Authentication header, not an Authorization header. You should not typically use a query parameter as it will likely get logged in Cloud Console.
When using "Authorization: Bearer ", you would need to use an access token obtained through OAuth 2.0 authentication.
This can be illustrated if you use the Oauth Playground agains any of the Google APIs.
Keep in mind that if you want to access your Firebase database using the Oauth Playground, you would need to configure the client ID and client Secret of your Firebase project on the gear icon at the top right of the playground screen.
Also make sure to use these scopes:
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/firebase.database
After completing all the steps, you will be able to make a REST request using the authorization header with the obtained access token.

Microsoft login from oauth2 issue

I have a React app using axios library for handling request. So following the next post:
How to login with username/password using OAuth2 and microsoft login and HTTP request
I could perform the action on Postman.
Then I set up the axios library to perform the POST
const dataForBody = `${'grant_type=password&' +
'username='}${encodeURI(userName)}&` +
`password=${encodeURI(userPassword)}&` +
`client_id=${encodeURI(clientID)}&` +
`resource=${encodeURI('https://graph.microsoft.com')}&` +
`client_secret=${encodeURI(clientSecret)}`;
const messageHeaders = {
'Content-Type': 'application/x-www-form-urlencoded'
};
axios({
method: 'post',
url: 'https://login.microsoftonline.com/{tenant}/oauth2/token',
headers: messageHeaders,
data: dataForBody,
})
.then((response) => {
});
but I get the following error:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at
https://login.microsoftonline.com/{tenant}/oauth2/token.
(Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
I tried adding:
'Access-Control-Allow-Origin': 'https://login.microsoftonline.com',
to the headers, but it did not work.
So adding Allow-Control-Allow-Origin: *​​​ chrome extension fixed my problem.
The thing is, my app is to be published on azure, so I tested the request on other web browsers and it did not work. So I don't want my users to install the extension.
Is there something wrong with my request? Why postman can do it without setting up headers?
Is there any other approach to achieve it?
PS: I read about using adal.js but I dont want to use the login screen from microsoft, because I know user and pass for the app, and I want to avoid manual login.
The problem you face is due to you trying to call the token endpoint via AJAX, which it won't accept due to the CORS header missing. You can't add it, it's missing from the response from Azure AD.
What you need to do is instead of getting the access token from the token endpoint, you must use the OAuth Implicit Grant Flow. This flow allows you to get the tokens directly in the authorization stage, and is especially designed for JavaScript-based apps. More info here: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-dev-understanding-oauth2-implicit-grant.
What this means is that you can't use the Password Grant Flow as you are doing now, unless you make the calls from your backend instead of the frontend.

Resources