NextJS API Handler unable to access HTTPOnly cookie - reactjs

I am using NextJS Request helpers to implement a proxy api.
Upon successful login I set an access_token within the getServerSideProps using cookies-next
export async function getServerSideProps(ctx) {
let login = await CompleteLogin(ctx.query.session);
if (login?.type === 200) {
deleteCookie("token", ctx);
setCookie("access_token", login.data.token, {
secure: true,
httpOnly: true,
res: ctx.res,
req: ctx.req,
expires: new Date(login.data.expiry),
path: "/",
});
}
return {
props: {
...returnProps,
},
};
}
The cookie set's no problem, I can see it being stored.
Subsequent requests then go via the proxy handler /api/[...path].ts, however within the proxy handler I am unable to get the access_token cookie.
import { getCookie } from "cookies-next";
import type { NextApiRequest, NextApiResponse } from "next";
import httpProxyMiddleware from "next-http-proxy-middleware";
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const access_token = getCookie("access_token", { req, res });
console.log("token api", access_token); // undefined
httpProxyMiddleware(req, res, {
headers: {
Authorization: `Bearer ${token}`,
},
target: process.env.API_URL,
});
};
export default handler;
Any idea where I am going wrong? I have implement this proxy style before and had no issues so I am assuming I have missed something quite obvious?
I have tried different ways of setting the cookie, custom proxy handler, but the only way I have been able to get it to work is not setting the cookie to be HTTPOnly, which defeats the need of the proxy.

Related

How to pass accessToken to Axios interceptor without Redux

I am trying to authorize a Next app using the existing Nodejs backend using a manual JWT strategy.
My backend issues an access token, and I'm trying to sign each request with accessToken using axios.interceptor where I set Bearer ${token}
// utils/axios.ts
const axiosPrivate = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
timeout: 1000,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
},
});
axiosPrivate.interceptors.request.use(
(config: AxiosRequestConfig): AxiosRequestConfig => {
if (config.headers === undefined) {
config.headers = {};
}
// no way to access sessionStorage or document.cookies
config.headers['Authorization'] = `Bearer ${accessToken}`
return config;
}
);
export { axiosPrivate }
My question is if there is some way to grab access token without Redux?
I want it in the interceptor because it will let me do SSR like that:
// pages/dashboard.tsx
export const getServerSideProps: GetServerSideProps = async (context) => {
const res = await axiosPrivate.get('/dashboard'); // request already signed
return {
props: {
dashboard: res.data,
},
};
};
You can pass the accessToken in the params for the axios request.
<code>await axiosPrivate.get('/dashboard', {
params: {
accessToken
}
});
</code>

Pass cookies through two Next.js projects API endpoints [duplicate]

Cookies are not sent to the server via getServerSideProps, here is the code in the front-end:
export async function getServerSideProps() {
const res = await axios.get("http://localhost:5000/api/auth", {withCredentials: true});
const data = await res.data;
return { props: { data } }
}
On the server I have a strategy that checks the access JWT token.
export class JwtStrategy extends PassportStrategy(Strategy, "jwt") {
constructor() {
super({
ignoreExpiration: false,
secretOrKey: "secret",
jwtFromRequest: ExtractJwt.fromExtractors([
(request: Request) => {
console.log(request.cookies) // [Object: null prototype] {}
let data = request.cookies['access'];
return data;
}
]),
});
}
async validate(payload: any){
return payload;
}
}
That is, when I send a request via getServerSideProps cookies do not come to the server, although if I send, for example via useEffect, then cookies come normally.
That's because the request inside getServerSideProps doesn't run in the browser - where cookies are automatically sent on every request - but actually gets executed on the server, in a Node.js environment.
This means you need to explicitly pass the cookies to the axios request to send them through.
export async function getServerSideProps({ req }) {
const res = await axios.get("http://localhost:5000/api/auth", {
withCredentials: true,
headers: {
Cookie: req.headers.cookie
}
});
const data = await res.data;
return { props: { data } }
}
The same principle applies to requests made from API routes to external APIs, cookies need to be explicitly passed as well.
export default function handler(req, res) {
const res = await axios.get("http://localhost:5000/api/auth", {
withCredentials: true,
headers: {
Cookie: req.headers.cookie
}
});
const data = await res.data;
res.status(200).json(data)
}

Reactjs Apollo Client Graphql doesn't always attach my refresh cookie in headers

I have a ReactJS application running locally (with https) on port 3000 and a Nest js Graphql lambda server runnning locally (http) on port 4000.
I have have used this article as an example to implement JWT token and cookie (for refresh token) based authentication.
My issue currently is that apollo client in the react application, does not always attach the cookies to the request and that happens randomly. I can see the cookie in the browser storage.
Apollo Client & Http Link set up
this.cache = new InMemoryCache()
this.apolloClient = new ApolloClient({
cache: this.cache,
link: this.authLink.concat(this.httpLink),
connectToDevTools: process.env.REACT_APP_STAGE === 'dev',
credentials: "include",
})
private readonly httpLink = createHttpLink({
uri: `${process.env.REACT_APP_API_URL}`,
credentials: 'include',
fetchOptions: {
credentials: 'include'
}
})
private readonly authLink = setContext((_, { headers }) => {
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: this.accessToken ? `Bearer ${this.accessToken}` : '',
},
}
})
Server Setup:
Graphql options passed to nest js Graphql module
import { GqlModuleOptions, GqlOptionsFactory } from '#nestjs/graphql';
import { Injectable } from '#nestjs/common';
import { join } from 'path';
#Injectable()
export class GraphqlOptions implements GqlOptionsFactory {
createGqlOptions(): Promise<GqlModuleOptions> | GqlModuleOptions {
if (process.env.STAGE === 'dev') {
return {
context: ({ req, res }) => {
return {
req,
res
}
},
autoSchemaFile: 'src/schema.graphql',
playground: {
endpoint: '/graphql',
},
engine: {
reportSchema: true,
},
cors: {
credentials: true,
origin: ["https://localhost:3000"],
}
}
} else {
return {
context: ({ req, res }) => ({ req, res }),
autoSchemaFile: '/tmp/schema.graphql',
playground: {
endpoint: '/graphql',
},
introspection: true
}
}
}
}
When a client requests a token for the first time i provide it in the response header using the bellow code in a mutation resolver:
context["res"].setHeader('Set-Cookie', [refreshToken.cookie])

Application does not fetch access token from cache using MSAL (react-aad-msal)

authProvider.getAccessToken() calls the authentication endpoint for every API call, instead of fetching it from the cache.
I don't know if the issue is with AcquireTokenSilent in Msal or getAccessToken in react-aad-msal.
Using msal 1.2.1 and react-aad-msal 2.3.2
Api call helper:
import { config } from '../config';
import { authProvider } from './../authProvider';
export const callApi = async (method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, data?: any) => {
const token = await authProvider.getAccessToken();
const res = await fetch(`${config.API_ENDPOINT}/api/${path}`, {
method,
headers: {
Authorization: 'Bearer ' + token.accessToken,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
return res.json();
};
Config:
import { MsalAuthProvider, LoginType } from 'react-aad-msal';
// Msal Configurations
const config = {
auth: {
authority: 'https://login.microsoftonline.com/<my tenant id>',
clientId: '<my client id>',
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false,
},
};
// Authentication Parameters
const authenticationParameters = {
scopes: ['offline_access'],
};
// Options
const options = {
loginType: LoginType.Redirect,
tokenRefreshUri: window.location.origin + '/auth.html',
};
export const authProvider = new MsalAuthProvider(config, authenticationParameters, options);
I stumbled to the same issue, try to add the required scope to the access token request.
const token = await authProvider.getAccessToken({
scopes: ['offline_access']
});
I fixed the problem by removing 'offline_access' from scopes, as it seems it's added implicitially, and adding it manually causes MSAL to not find the cached token as the scopes are used as key.
I had to add my custom scopes as well, in my case 'User.Read'
const authenticationParameters = {
scopes: ['User.Read'],
};
const tokenResponse = await MsalInstance.acquireTokenSilent({
scopes: ['User.read', 'whatyouwant','whatyouneed','whatyoudeepdesire'],
account: MsalInstance.getAccountByUsername(username) , // or MsalInstance.getAllAccounts()[0].username
forceRefresh: true
}).then(data=>{
if (data.idTokenClaims) {
console.log(data.idTokenClaims.exp)
}
})

how to send cookies with axios post method using react Js

I am using react js for front end and spring boot for backend process. I need to send cookies from front end to backend for which I am trying following approach using axios:
Frontend
async function submitPageData() {
try {
const jwttoken = {
headers: {
jwt: to12,
Cookie: token
}
};
const response = await axios
.put(
url,
{
id: pageId,
projectName: PROJECT_NAME,
title: title
},
jwttoken
)
.then(function () {
});
} catch (error) {
}
}
}
}
And receiving cookies at backend using #CookieValue annotation but as I checked and found that my request header is not carrying Cookie with it.
Please guide me, how can I send cookie from react js axios method so that I will able to receive it at backend.
Edit
const jwttoken = {
headers: {
jwt: to12,
Cookie: token,
withCredentials: true
}
Thanks in advance!
For some reason, using withCredentials as a header doesn't work for me either.
This to add it like that:
import axios from "axios";
axios.defaults.withCredentials = true;

Resources