Nextjs authenticate in a middleware with jwt doesn't work [duplicate] - reactjs

This question already has answers here:
Nextjs build failing because of jsonwebtoken in _middleware.ts
(3 answers)
Closed 7 months ago.
In this moment I authenticate user in each API. I want to add a middleware to do it only once.
So I created a file named _middleware.ts under /pages/api and used the same approach I did for every API.
I have the following code:
_middleware.ts
import { NextApiRequest } from 'next';
import type { NextFetchEvent, NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { getUserIdOrFail } from './utils/jwt';
export async function middleware(req: NextRequest, ev: NextFetchEvent) {
const authToken = req.headers.get('Authorization');
let userId: string;
try {
userId = await getUserIdOrFail({
headers: { authorization: authToken },
} as NextApiRequest);
} catch (err) {
//return 401
}
// here usereId might contain the userId if correctly authenticated
return NextResponse.next();
}
jwt.ts
import { sign, verify } from 'jsonwebtoken';
import { config } from './config';
import { User } from '../models/user';
import { jwtSchema } from './schemas/jwtSchema';
import { NextApiRequest } from 'next';
export const buildJwt = (user: User) =>
sign({ id: user._id }, config.JWT_KEY, { expiresIn: config.JWT_EXPIRATION });
export const verifyJwt = <T>(jwt: string) => {
try {
return verify(jwt, config.JWT_KEY) as T;
} catch (e) {
return null;
}
};
export async function getUserIdOrFail(req: NextApiRequest) {
const authorizationHeader = req.headers.authorization;
if (!authorizationHeader || !authorizationHeader.startsWith('Bearer ')) {
throw new Error();
}
const jwt = authorizationHeader.replace('Bearer ', '');
const parsed = verifyJwt(jwt);
const { value, error } = jwtSchema.validate(parsed);
if (error) {
throw new Error();
}
return value.id as string;
}
Basically with this code I get the bearer token that server gave to the user when he logged in or registered contained into the Authorization header and verify if it is valid. If it is valid I take the userId and use it to do operations with the DB.
The problem is that it does the stuff if I use it in the api files, but in this file it goes into error (catch block with this error). Is it a jsonwebtoken problem? Should I to do it differently? Or should I leave it in the way it actually is?

The problem with this approach is that the jasonwebtoken library uses Dynamic code evaluation which is not allowed into the file _middleware.ts in next.js. To solve this need to use another library or do it in the api files.

Related

SvelteKit Server Side Authentication with Supabase not creating cookie with Token

I'm trying to implement Server Side Authentication with Supabase and Sveltekit. I followed the Quickstart Guide and was able to do authentication client-side. (preventDefault on the submit event and do client-side POST request).
But when trying to do the same thing Server-Side , the auth cookie with the token is not created. Here's the logic:
// src/routes/login/+page.server.ts
import type { PostgrestResponse } from '#supabase/supabase-js';
import { supabaseClient } from '$lib/supabaseClient';
import type { Database } from '$lib/types/database.types';
import type { PageLoad } from './$types';
import type { PageServerLoad, Actions } from './$types';
import { redirect } from '#sveltejs/kit';
export const actions: Actions = {
'login-with-password': async ({ request }) => {
const formData = await request.formData();
const email = formData.get('email');
const password = formData.get('password');
console.log(email, password);
const { data, error } = await supabaseClient.auth.signInWithPassword({ email, password });
console.log(data);
if (error) {
return {
status: 500,
body: {
error: error.message
}
};
}
throw redirect(302, '/');
return { success: true };
}
};
data seems to hold the correct response, with token and everything, but that's not persisted as a cookie.
https://stackblitz.com/~/github.com/gkatsanos/client

How to get session in Next.js middleware? (error in deploy)

import type { NextFetchEvent, NextRequest } from "next/server";
import { getSession } from "next-auth/react";
import { NextResponse } from "next/server";
export async function middleware(req: NextRequest, ev: NextFetchEvent) {
const requestForNextAuth = {
headers: {
cookie: req.headers.get("cookie"),
},
};
//#ts-ignore
const session = await getSession({ req: requestForNextAuth });
if (
req.nextUrl.pathname.startsWith("/fictions/create") &&
(!req.cookies.get("~~session") || !session)
) {
return NextResponse.rewrite(new URL("/enter", req.url));
}
if (
req.nextUrl.pathname.includes("/edit") &&
(!req.cookies.get("~~session") || !session)
) {
return NextResponse.rewrite(new URL("/enter", req.url));
}
if (req.nextUrl.pathname.startsWith("/profile") && !session) {
if (!session) {
return NextResponse.rewrite(new URL("/enter", req.url));
}
}
}
Error Message :
"Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime
Learn More: https://nextjs.org/docs/messages/edge-dynamic-code-evaluation"
It worked well with local but seems I did something wrong because it seems to cause errors in when deploying project.
I want unauthorized people redirected to '/enter' page by using next-auth session.
So I used getSession.
Is it wrong way to get session in 'edge'?
Then what I should do for?
If I understood well you are trying to check in _middleware.js whether the current user is logged in or not ?
You cannot use getSession() here.
Here is my workaround, it's working in local (didn't try in production yet) :
export async function middleware(req) {
const pathname = req.nextUrl.pathname
const session = await getToken({ req: req, secret: process.env.NEXTAUTH_SECRET }); // I am getting the session here
// Protect protected pages
if (arrayOfProtectedPaths.includes(pathname)) {
if (session === null) {
return NextResponse.redirect("http://localhost:3008/spots/allSpots")
}
}
// Prevent logged in user to access to register and sign in
if (shouldNotBeUser.includes(pathname)) {
if (session !== null) {
return NextResponse.redirect("http://localhost:3008/spots/allSpots")
}
}
}

SvelteKit - getSession and handle functions in hook.ts aren't executing

As it already says in the title, the handle and getSession functions in my project don't execute.
Here is my hook.ts file:
import type { GetSession, Handle } from "#sveltejs/kit"
import {parse} from "cookie"
export const handle: Handle = async({event, resolve})=>{
console.log("Handle")
const cookie = await parse(event.request.headers.get("cookies") || "")
event.locals.user.lang = cookie.lang || "sl"
const response = await resolve(event)
return response;
}
export const getSession: GetSession = async(request) => {
return {
user: {
lang: request.locals.user.lang
}
}
}
I don't know if I missed something that's crucial for it to execute, but yeah. I searched a long time for a solution but I did not find anything, even if I followed some tutorials step by step.

Next.js imported function errors out with 'ReferenceError' in getServerSideProps

I am using Firebase for auth in my project. After user authenticates, I set his/her id token in cookies, so that next time any request is made to auth-only page, I can verify the token server-side for SSR.
However, the wrapper function I wrote for this errors out as 'ReferenceError' when used in getServerSideProps.
lib/firebase-admin.ts
import { initializeApp, App, AppOptions } from 'firebase-admin/app'
import { getAuth, Auth } from 'firebase-admin/auth'
import { credential } from 'firebase-admin'
import serviceAccount from '../secrets/firebase-admin-sdk.json'
// Firebase Admin app configs
const firebaseAdminConfig: AppOptions = {
credential: credential.cert(JSON.stringify(serviceAccount))
}
// Get app admin instance and export it
const app: App = initializeApp(firebaseAdminConfig)
export default app
// Get auth admin and export
export const auth: Auth = getAuth(app)
utils/auth-server.ts
import { auth } from '../lib/firebase-admin'
import { DecodedIdToken } from 'firebase-admin/auth'
import AuthErrorMessages from '../constants/auth'
// Export function to verify id token in server side
interface IVerifyIdToken {
status: boolean
message?: string
token?: DecodedIdToken
}
export const verifyIdToken = async (idToken: string): Promise<IVerifyIdToken> => {
try {
const decodedIdtoken = await auth.verifyIdToken(idToken, true)
console.log(decodedIdtoken)
return { status: true, token: decodedIdtoken }
} catch (e) {
return { status: false, message: e }
}
}
components/test.tsx
import { GetServerSideProps, GetServerSidePropsContext, InferGetServerSidePropsType } from 'next'
import nookies from 'nookies'
import { verifyIdToken } from '../utils/auth-server'
export const getServerSideProps: GetServerSideProps = async (ctx: GetServerSidePropsContext) => {
const cookies = nookies.get(ctx)
if (cookies.token) {
const idToken = await verifyIdToken(cookies.token) // ERROR HERE
console.log(idToken)
return {
props: {
email: 'DUMMY'
}
}
} else {
return {
props: {
email: "NO EMAIL (not logged in)"
}
}
}
}
export default function Test({ email }: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
<p>Your email: {email}</p>
)
}
Error while opening /test
ReferenceError: Cannot access 'auth' before initialization
at Module.auth (webpack-internal:///./lib/firebase-admin.ts:5:53)
at verifyIdToken (webpack-internal:///./utils/auth-server.ts:12:87)
at getServerSideProps (webpack-internal:///./pages/test.tsx:20:96)
at Object.renderToHTML (/home/captain-woof/Desktop/charity-cms/node_modules/next/dist/server/render.js:479:26)
at runMicrotasks (<anonymous>)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async doRender (/home/captain-woof/Desktop/charity-cms/node_modules/next/dist/server/next-server.js:1392:38)
at async /home/captain-woof/Desktop/charity-cms/node_modules/next/dist/server/next-server.js:1487:28
at async /home/captain-woof/Desktop/charity-cms/node_modules/next/dist/server/response-cache.js:63:36
I fixed the problem! (thanks #ArneHugo the hint)
So, what happened was not really a cyclic dependency, but files getting compiled asynchronously, because of which there was no actual control over what got compiled first.
I fixed this by making a small change:
lib/firebase-admin.ts
.
.
.
const serviceAccount = require('../secrets/firebase-admin-sdk.json') // Earlier -> import serviceAccount from '../secrets/firebase-admin-sdk.json'
.
.
.
credential: credential.cert(serviceAccount) // Earlier -> credential: credential.cert(JSON.stringify(serviceAccount))
.
.
.
// REPLACE ENTIRE BELOW PORTION WITH THIS
// Get app admin instance and export it
if (getApps().length === 0) { // To make sure only one instance is created and referred to at a time
initializeApp(firebaseAdminConfig)
}
// Get auth admin and export
export const auth: Auth = getAuth(getApp()) // To make sure auth from only the one app instance we have is exported

Server-side redirects using react HOC when a httpOnly cookie is not present [duplicate]

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 }

Resources