I am trying to hide one of my routes in NextJS behind firebase Auth. I am storing a token in the context and retrieving the token in getServerSideProps. This works in development but once deployed to Vercel it fails.
I see the cookie is there after logging in but the try block fails.
I'm wondering if it's a race condition in the production env but I am using async await.
Here is the bloc that is failing.
export async function getServerSideProps(context) {
//console.log(context)
try {
const cookies = nookies.get(context);
//const cookies = Cookies.get(context);
const token = await verifyIdToken(cookies.token);
const {uid, email} = token;
//console.log("Token in vendors: ", token);
return {
props: {
session: `Email: ${email} IUD: ${uid}`
}
}
} catch (err) {
context.res.writeHead(302, {location: "/"}); //login //change to welcome page
context.res.end();
return (
{ props: [] }
)
}
}
I am using nookies and I even tries switching to cookie-js as someone suggested.
Any help would be greatly appreciated.
here is the repo:
https://github.com/craigbauerwebdev/next-tartanbook
The token is set here:
https://github.com/craigbauerwebdev/next-tartanbook/blob/master/components/Auth/Auth.js
The Token is being retrieved here:
https://github.com/craigbauerwebdev/next-tartanbook/blob/master/pages/vendors/index.js
Related
I've tried to check for session on server side in my NextJS project. I've used supabaseService client and also "createServerSupabaseClient" from #supabase/auth-helpers-nextjs both of those checks returns session null even tho I'm logged in in the client...
export async function getServerSideProps(
context: GetServerSidePropsContext<{ tournamentId: string }>
) {
// Create authenticated Supabase Client
const supabaseServ = await supabaseService.auth.getSession();
const supabase = await createServerSupabaseClient(context);
// Check if we have a session
const {
data: { session },
} = await supabase.auth.getSession();
console.log("sessions", supabaseServ, session);
if (!session)
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
Does anyone know why am I unable to getSession on SSR?
Also I've console logged the cookies and it seems that I do have the JWT cookie in the request context...
I was creating authentication mechanism for my service. And at some moment I had problem with cookies. More you can find here, so I solved this.
The problem was that I was trying to send cookie through 2 requests. My Next.js front-end sends request to its internal API, and only then, internal API sends this request to back-end.
The solution of this problem was very easy, what I had to do - is to set cookie on back-end and return it in headers. Here is how flow looks, like.
This is how it looks like, endpoint in Next.js front-end. Except of data in response, it receives header, where cookie is set (response from back-end) and send it in header of response, that will be send on front-end, where cookie will be set:
import { NextApiRequest, NextApiResponse } from "next";
import { AxiosError } from "axios";
import { api } from "../../../api";
export default async (
req: NextApiRequest,
res: NextApiResponse
) => {
try {
const { data, headers } = await api.post('/user/sign-in', req.body)
if (headers["set-cookie"]) {
res.setHeader("Set-Cookie", headers["set-cookie"]);
}
return res.json(data)
} catch (error) {
return res
.status((error as AxiosError).response?.status as number)
.json((error as AxiosError).response?.data);
}
}
And endpoint on back-end:
import { Response as Res } from 'express';
import * as dayjs from 'dayjs';
...
async signIn(#Body() signInUserDto: SignInUserDto, #Response() res: Res) {
const { _at, _rt } = await this.userService.signIn(signInUserDto);
res.cookie('_rt', _rt, {
httpOnly: true,
expires: dayjs().add(7, 'days').toDate()
});
return res.send(_at);
}
And here is the problem, because of this Response class of express I keep getting this warning:
Error: This is caused by either a bug in Node.js or incorrect usage of Node.js internals.
Please open an issue with this stack trace at https://github.com/nodejs/node/issues
at new NodeError (node:internal/errors:371:5)
at assert (node:internal/assert:14:11)
at ServerResponse.detachSocket (node:_http_server:249:3)
at resOnFinish (node:_http_server:819:7)
at ServerResponse.emit (node:events:390:28)
at onFinish (node:_http_outgoing:830:10)
at callback (node:internal/streams/writable:552:21)
at afterWrite (node:internal/streams/writable:497:5)
at afterWriteTick (node:internal/streams/writable:484:10)
at processTicksAndRejections (node:internal/process/task_queues:82:21)
It is definitely because of how this signIn function looks like, because I was trying to return just like this - return this.userService.signIn(signInUserDto) - and it worked, but I can't cookie in this case.
So, my question is - what is this error? Can I just ignore it? If not, then how can I fix it?
Thanks in advance!
TL;DR
Finally, I was able to fix this error, first of all, as I said, my goes through 2 API's, from back-end to front-end API, and only then, this front-end API sends this request to actual front-end.
So, what I did, is just returned 2 tokens - refresh and access - as body.
#ApiOperation({ summary: 'Resource for sign in user.' })
#ApiResponse({ status: 200, type: TokensDto })
#Post('/sign-in')
async signIn(#Body() signInUserDto: SignInUserDto) {
return this.userService.signIn(signInUserDto);
}
Then, on front-end, I installed cookie and #types/cookie and in this front-end endpoint, in headers, I just serialized this refresh token from body payload, and removed from it.
import { NextApiRequest, NextApiResponse } from "next";
import { AxiosError } from "axios";
import { api } from "../../../api";
import { serialize } from 'cookie';
export default async (
req: NextApiRequest,
res: NextApiResponse
) => {
try {
const { data } = await api.post('/user/sign-in', req.body)
res.setHeader('Set-Cookie', serialize(
'_rt',
data._rt,
{ path: '/', httpOnly: true })
);
delete data._rt
return res.json(data)
} catch (error) {
return res
.status((error as AxiosError).response?.status as number)
.json((error as AxiosError).response?.data);
}
}
And it works perfectly fine, I don't have this Node.js error any more because of response with Express response class, and I'm able to set cookie.
EDIT
I have improved this code in even better way by using fastify and in the whole pipeline cookie is set in header. First of all, on back-end install #fastify/cookie and #nestjs/platform-fastify. Then, add this in file, where you start you Nest.js app:
import {
FastifyAdapter,
NestFastifyApplication
} from '#nestjs/platform-fastify';
import { fastifyCookie } from '#fastify/cookie';
async function bootstrap() {
const PORT = process.env.PORT || 3002;
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter()
);
await app.register(fastifyCookie, {
secret: 'my-secret'
});
This will allow you to use FastifyReply from fastify, this will eliminate this Node.js error as response class:
import { FastifyReply } from 'fastify';
#ApiTags('User')
#Controller('user')
export class UserController {
constructor(private userService: UserService) {}
#Post('/sign-in')
async signIn(
#Body() signInUserDto: SignInUserDto,
#Res({ passthrough: true }) res: FastifyReply
) {
const { _at, _rt } = await this.userService.signIn(signInUserDto);
res.setCookie('_rt', _rt);
return res.send(_at);
}
...
And the last step, on front-end endpoint, using cookie, parse this cookie and send it to front.
const { data, headers } = await api.post('/user/sign-in', req.body)
if (headers["set-cookie"]) {
const refreshToken = headers["set-cookie"][0].split('=')[1];
res.setHeader('Set-Cookie', serialize(
'_rt', refreshToken, { path: '/', httpOnly: true })
);
}
return res.json(data)
And this is the best way, that I've found, because it allows you to send cookie in header though all pipeline, not in body and then delete it, and this solution eliminates this strange Node.js error.
I'm having troubled sending an authenticated request to my API immediately after signing in to my Nextjs app using NextAuth. The request that is sent after signing in returns data for and unauthenticated user.
I believe the issue is that React Query is using a previous version of the query function with an undefined jwt (which means its unauthenticated). It makes sense because the query key is not changing so React Query does not think it's a new query, but, I was under the impression that signing in would cause loading to be set to true temporarily then back to false, which would cause React Query to send a fresh request.
I've tried invalidating all the queries in the app using queryClient, but that did not work. I've also used React Query Devtools to invalidate this specific query after signing in but it still returns the unauthenticated request. Only after refreshing the page does it actually send the authenticated request.
// useGetHome.js
const useGetHome = () => {
const [session, loading] = useSession();
console.log(`session?.jwt: ${session?.jwt}`);
return useQuery(
'home',
() => fetcher(`/home`, session?.jwt),
{
enabled: !loading,
},
);
}
// fetcher
const fetcher = (url, token) => {
console.log(`token: ${token}`);
let opts = {};
if (token) {
opts = {
headers: {
Authorization: `Bearer ${token}`,
},
};
}
const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}${url}`, opts);
if (!res.ok) {
const error = await res.json();
throw new Error(error.message);
}
return res.json();
}
// Home.js
const Home = () => {
const { data: home_data, isLoading, error } = useGetHome();
...
return(
...
)
}
Attached is the console immediately after signing in. You can see the the session object contains the jwt after signing in, but in the fetcher function it is undefined.
console after signing in
Any help here is appreciated. Is there a better way to handle authenticated requests using React Query and NextAuth? Thank you!
I have tried a similar situation here and struggled the same thing but the enabled property worked fine for me and it is good to go right now.
https://github.com/maxtsh/music
Just check my repo to see how it works, that might help.
I'm using the NextAuth library for authentication in a Next.js app and I've come across a scenario which hasn't been addressed in the docs. I'm trying to add a 'route guard' if you can call it that by checking if session is not null in getServerSideProps and redirecting to the built in signin page. Although that works, I'm always redirected to the home page after signing in. I tried to add the callbackUrl query parameter as you can see below but it still doesn't work as expected. How do I achieve this? Thanks in advance.
blog.js
export async function getServerSideProps(context) {
const session = await getSession(context)
if (!session) {
return {
redirect: {
destination: `/api/auth/signin?callbackUrl=${encodeURIComponent(
'http:localhost:3000/blog'
)}`,
permanent: false
}
}
}
return {
props: {
data: 'Blog data',
session
}
}
}
Now that your login page has a query string:
You need to pull the callbackUrl from url (I would suggest giving it a good default such as "/dashboard")
You need to pass a callbackURL to your signIn function (that I assume you are calling instead of a post method) from your onSubmit function.
const { query:{ callbackUrl } } = useRouter();
signIn("your-provider-name", {
...logInFormValues, // such as email and password
callbackUrl, // from the useRouter hook
});
I am using the following:
https://github.com/XeroAPI/xero-node
I am using a React app, talking to a Nodejs backend. The React app calls the node file connect.js as per the below:
// connect.js (node module)
const XeroClient = require('xero-node').XeroClient;
async function connect(req, res, next) {
try {
const xero = new XeroClient({
clientId: '9.............(hidden for SO)',
clientSecret: 'p...........(hidden for SO)',
redirectUris: [`http://localhost:3000/xeroCallback`],
scopes: 'openid profile email accounting.transactions offline_access'.split(" ")
});
let consentUrl = await xero.buildConsentUrl();
res.send(consentUrl);
} catch (err) {
console.log(err);
}
}
module.exports = connect;
This returns the URL to my React front end, which triggers a re-direct
This works fine, I am taking to the Xero Auth page, which then re-directs me back to localhost, where my React frontend calls on .callback.js from the back end, sending along the URL past from Xero:
{"http://localhost:3000/xeroCallback?code":"3......(hidden for SO)","scope":"openid profile email accounting.transactions","session_state":"E.........(hidden for SO)"}
Here is my code in callback.js
// callback.js (node module)
const { TokenSet } = require('openid-client');
const XeroClient = require('xero-node').XeroClient;
async function callback(req, res) {
const xero = new XeroClient({
clientId: '9.............(hidden for SO)',
clientSecret: 'p...........(hidden for SO)',
redirectUris: [`http://localhost:3000/xeroCallback`],
scopes: 'openid profile email accounting.transactions offline_access'.split(" ")
});
try {
await xero.initialize()
const TokenSet = await xero.apiCallback(req.body);
res.send(TokenSet);
} catch (err) {
console.log(err);
}
}
module.exports = callback;
There error is at "const TokenSet = await xero.apiCallback(req.body);" Gives me 'Access token undefined!"
So the error is because the Xero client has not yet been properly initialized.
https://github.com/XeroAPI/xero-node/blob/master/src/XeroClient.ts#L99
As you can see on the code below (linked above) the callbackParams function is a method on the openIdClient ( an oauth2.0 library ) - in order to fully setup they client you will need to call either xero.initialize() or xero.buildConsentUrl() It also looks like you should be passing back the req.url, though the req.body might still work..
this.openIdClient.callbackParams(callbackUrl)
It is setup this way to allow for more varied use cases for folks who do not need/want to access the helpers by requiring the openid-client.
// This needs to be called to setup relevant openid-client on the XeroClient
await xero.initialize()
// buildConsentUrl calls `await xero.initialize()` so if you wont
// need to also call initialize() if you are sending user through auth
await xero.buildConsentUrl()
// You can also refresh the token without needing to initialize the openid-client
// helpful for background processes where you want to limit any dependencies (lambda, etc)
await xero.refreshWithRefreshToken(client_id, client_secret, tokenSet.refresh_token)
https://github.com/XeroAPI/xero-node