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)
}
Related
Can't get the session data using getSession({req}) on a api call?
useSession() on a component is working fine.
package versions: nextjs#12.01, next-auth#4.3.1
issue: api/lists.ts:
import prisma from "../../../lib/prisma";
import { getSession } from "next-auth/react"
export default async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getSession({ req });
console.log('session data is always null', session);
nexuauth.js config file:
callbacks: {
session: async ({ session, user, token }) => {
if (session?.user) {
session.user.id = user.id;
}
// console.log('session callback', session);
return session;
},
},
session: { strategy: "database" },
_app.js file:
function MyApp({ Component, ...pageProps }) {
return (
<SessionProvider session={pageProps.session} refetchInterval={5 * 60}>
<Main>
<Component {...pageProps} />
</Main>
</SessionProvider>
)
}
export default MyApp
on a login.js file (working fine):
export default function Login() {
const { data: session } = useSession()
Session depends on cookies, your browser automatically sends the cookies. So useSession(), which is on client side/browser always work. But when you use getSession cookies are not present automatically, we need to set them manually (github issue)-
const response = await fetch(url, {
headers: {
cookie: req.headers.cookie || "",
},
});
To add these automatically you can perhaps plug into interceptors (axios, fetch) or make a wrapper function.
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)
}
GOAL: have my api gateway get the httponly cookies being returned from my rest endpoints and pass it along to frontend, also the front end should be able to pass the cookies through.
httpO=httponly
SPA(react) apiGateway(apolloQL) restEndpoint
httpO-cookies----> <-----(httpO)cookies-----> <-----(httpO)cookies
current the resolvers I have are able to see the "set-cookies" in the response from the endpoints but throughout the response lifecycle the header's are lost.
const apolloServer: ApolloServer = new ApolloServer({
context: ({ res }) => {
// console.log(res,"res");
return ({
res
});
},
formatError,
resolvers,
typeDefs,
formatResponse: (response: GraphQLResponse) => {
console.log(response.http?.headers, "header?");
return {
headers: {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Credentials': 'true',
},
data: response.data,
errors: response.errors,
};
}
});
ex. of resolver:
const signupUser = async (parent: any, args: any, context: any, info: any) => {
try {
const response = await UserService.createUser(args);
console.log(response , "response");
return response;
} catch (error) {
console.log(error
}
};
in this example lets assume the UserService.createUser return the entire response Object, not response.data.
const signupUser = async (parent: any, args: any, context: any, info: any) => {
try {
//call get the Express response from the context
const contextResponse = context.res
//or
const {res} = context
//then set the cookies to the response
const response = await UserService.createUser(args);
contextResponse.header('set-cookie', response?.headers['set-cookie']);
//return the original data
return response?.data;
} catch (error) {
console.log(error
}
};
I have hosted a JWT auth API on heroku. While accessing the API using axios from my front-end React app, I notice that the protected routes can never be fetched since they are dependent on res.cookies which are never set. While testing the API out on postman, it sets the cookies and everything works fine. Can you help me finding where I am going wrong and how I can overcome this.
Sample cookie that is supposed to be set(working fine with postman)
This cookie is needed, because when I GET /user, with the following code:
exports.checkUser = catchAsync(async (req, res, next) => {
let currentUser;
if (req.cookies.jwt) {
const token = req.cookies.jwt;
const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
currentUser = await User.findById(decoded.id);
} else {
currentUser = null;
}
res.status(200).send({ currentUser });
});
currentUser should be populated as follows
When I access this API using my react frontend: this being my useAuth hook
import { useState, useContext } from "react";
import { useHistory } from "react-router-dom";
import axios from "axios";
import { UserContext } from "./userContext";
export default function useAuth() {
let history = useHistory();
const { setUser } = useContext(UserContext);
const [error, setError] = useState(null);
const setUserContext = async () => {
return await axios
.get("<my api link>/user")
.then((res) => {
console.log(res);
setUser(res.data.currentUser);
history.push("/home");
})
.catch((err) => {
console.log(err);
setError(err);
});
};
const registerUser = async (data) => {
const { username, email, password, passwordConfirm, name } = data;
return axios
.post("<My api link>/auth/post", {
username,
email,
password,
passwordConfirm,
name,
})
.then(async (res) => {
console.log(res);
await setUserContext();
})
.catch((err) => {
console.log(err);
return setError(err);
});
};
res.data.currentUser is ALWAYS null. Any help is appreciated
The concept of JWT is that the handling of the token is handed over to the client.
Hence, the frontend is supposed to store/save the JWT Token it receives in cookies or localstorage and is supposed to send them via headers in requests where you want to access such protected routes.
Hence it's the frontend duty and that's why Postman handles it automatically for you.
You can use react-cookie to save and retrieve this JWT token in the frontend whenever required and you will have to reform your axios request.
In your specific case, you can do the following for GET request:
axios.get('URL', {
withCredentials: true
});
But I would strongly recommend amending your backend to extract JWT tokens from headers instead of cookies, which would make your request, something similar to:
let JWTToken = 'xxyyzz'; // Get this from cookie or localstorage, hardcoded for demonstration.
axios
.get("URL", { headers: {"Authorization" : `Bearer ${JWTToken}`} })
.then(res => {
this.profile = res.data;
console.log('Fetched Data is', res.data);
})
.catch(error => console.log(error))
Do not forget to enable CORS on your backend!
So I'm creating authentication logic in my Next.js app. I created /api/auth/login page where I handle request and if user's data is good, I'm creating a httpOnly cookie with JWT token and returning some data to frontend. That part works fine but I need some way to protect some pages so only the logged users can access them and I have problem with creating a HOC for that.
The best way I saw is to use getInitialProps but on Next.js site it says that I shouldn't use it anymore, so I thought about using getServerSideProps but that doesn't work either or I'm probably doing something wrong.
This is my HOC code:
(cookie are stored under userToken name)
import React from 'react';
const jwt = require('jsonwebtoken');
const RequireAuthentication = (WrappedComponent) => {
return WrappedComponent;
};
export async function getServerSideProps({req,res}) {
const token = req.cookies.userToken || null;
// no token so i take user to login page
if (!token) {
res.statusCode = 302;
res.setHeader('Location', '/admin/login')
return {props: {}}
} else {
// we have token so i return nothing without changing location
return;
}
}
export default RequireAuthentication;
If you have any other ideas how to handle auth in Next.js with cookies I would be grateful for help because I'm new to the server side rendering react/auth.
You should separate and extract your authentication logic from getServerSideProps into a re-usable higher-order function.
For instance, you could have the following function that would accept another function (your getServerSideProps), and would redirect to your login page if the userToken isn't set.
export function requireAuthentication(gssp) {
return async (context) => {
const { req, res } = context;
const token = req.cookies.userToken;
if (!token) {
// Redirect to login page
return {
redirect: {
destination: '/admin/login',
statusCode: 302
}
};
}
return await gssp(context); // Continue on to call `getServerSideProps` logic
}
}
You would then use it in your page by wrapping the getServerSideProps function.
// pages/index.js (or some other page)
export const getServerSideProps = requireAuthentication(context => {
// Your normal `getServerSideProps` code here
})
Based on Julio's answer, I made it work for iron-session:
import { GetServerSidePropsContext } from 'next'
import { withSessionSsr } from '#/utils/index'
export const withAuth = (gssp: any) => {
return async (context: GetServerSidePropsContext) => {
const { req } = context
const user = req.session.user
if (!user) {
return {
redirect: {
destination: '/',
statusCode: 302,
},
}
}
return await gssp(context)
}
}
export const withAuthSsr = (handler: any) => withSessionSsr(withAuth(handler))
And then I use it like:
export const getServerSideProps = withAuthSsr((context: GetServerSidePropsContext) => {
return {
props: {},
}
})
My withSessionSsr function looks like:
import { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
import { IronSessionOptions } from 'iron-session'
const IRON_OPTIONS: IronSessionOptions = {
cookieName: process.env.IRON_COOKIE_NAME,
password: process.env.IRON_PASSWORD,
ttl: 60 * 2,
}
function withSessionRoute(handler: NextApiHandler) {
return withIronSessionApiRoute(handler, IRON_OPTIONS)
}
// Theses types are compatible with InferGetStaticPropsType https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
function withSessionSsr<P extends { [key: string]: unknown } = { [key: string]: unknown }>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) {
return withIronSessionSsr(handler, IRON_OPTIONS)
}
export { withSessionRoute, withSessionSsr }