next js middleware error on getting the query from the url - next.js

im trying to take the token from the url of a route in my site but the middleware log 3 times the token and in the second time the token is null
while i logged the req url my url that im in is http://localhost:3000/auth/newPassword
but in the second render its http://localhost:3000/_next/static/chunks/pages/auth/newPassword.js?ts=1667894719054
someone knows what is the problem here?
i sent an email for new password to the user email with query token in the url
i wan the middleware to check if the token is valid before accessing the new password route
and then verify it there but its render null for me in the second time and that crashed my project
import { NextResponse, NextRequest } from "next/server";
import { verify } from "jsonwebtoken";
const secret = process.env.JWT_SECRET!;
export default async function middleware(req: NextRequest) {
const url = req.url;
const token = await req.nextUrl.searchParams.get("token")!;
const cookies = req.cookies;
if (url.includes("/auth/newPassword")) {
console.log(url);
if (token === undefined) {
return NextResponse.redirect("http://localhost:3000/auth/signin");
}
try {
verify(token, secret);
return NextResponse.next();
} catch (e) {
return NextResponse.redirect("http://localhost:3000/auth/signin");
}
}
}

That because when you load http://localhost:3000/auth/newPassword next will make a request to http://localhost:3000/_next/static/chunks/pages/auth/newPassword.js?ts=1667894719054 (i guess for hydration), this file basically contains only react related javascript stuff, and you wont that your middleware will match this route, while currently it is (because it contains auth/newPassword).
I sueggest to use middleware with negative matchers :
export const config = {
matcher: [
/*
* Match all paths except for:
* 1. /api routes
* 2. /_next (Next.js internals)
* 3. /fonts (inside /public)
* 4. /examples (inside /public)
* 5. all root files inside /public (e.g. /favicon.ico)
*/
'/((?!api|_next|fonts|500|examples|[\\w-]+\\.\\w+).*)',
],
};
export default function middleware(req: NextRequest) {
const url = req.url;
const token = await req.nextUrl.searchParams.get("token")!;
const cookies = req.cookies;
if (url.includes("/auth/newPassword")) {
console.log(url);
if (token === undefined) {
return NextResponse.redirect("http://localhost:3000/auth/signin");
}
try {
verify(token, secret);
return NextResponse.next();
} catch (e) {
return NextResponse.redirect("http://localhost:3000/auth/signin");
}
}
}

Related

How to implement iron-session with session id

I am using iron-session, next-connect with nextjs in our webapp and one of the requirements is to publish analytics events from our frontend code, like page views, button clicks and other custom events. These events are stored in our database and used by our data analyst with PowerBI.
Our webapp takes a user on an onboarding journey, then once it's done, we create an account for the user and redirects to dashboard. For the onboarding part, we don't have a user id yet while in the dashboard, we already do. However, we want to be able to track the user journey in the webapp so we need an identifier that is persisted throughout the whole journey. Thus, we think of a session id with the iron-session.
Now iron-session doesn't have a concept of session id, so I am trying to implement it myself. The session id will be our identifier of the user in our events table.
Here is the withSession middleware used with next-connect
import { getIronSession } from "iron-session";
import type { IncomingMessage } from "http";
import type { NextApiRequest } from "next";
import { nanoid } from "nanoid";
import appConfig from "#/backend/app.config";
export const sessionOptions = {
password: appConfig.secret,
cookieName: appConfig.cookies.sessionToken.name,
cookieOptions: appConfig.cookies.sessionToken.options,
};
export async function withSession(
req: IncomingMessage | NextApiRequest,
res: any,
next: any
) {
const session = await getIronSession(req, res, sessionOptions);
if (!session.id) session.id = nanoid(32);
req.session = session;
await req.session.save();
return next();
}
declare module "iron-session" {
interface IronSessionData {
user?: { id: string };
id: string;
}
}
And a route that will use the middleware
const router = createRouter<NextApiRequest, NextApiResponse>()
.use(...([withSession, withLogger, withTenant] as const))
.get(async (req, res) => {
// Authenticate user
req.session.user = { id: userId };
await req.session.save();
return res.redirect("/");
});
export default router.handler();
Is this a correct implementation of the said requirement?
Some libraries implement a kind of session.regenerate() when a user perform signIn and signOut. Do I need to implement it too? If I do, I will lose the identifier that persists throughout the whole user journey.
since you are using typescript first define the type of session object
declare module "iron-session" {
interface IronSessionData {
nameOfSessionObject?: {
// in your implementation you were creating req.user and req.id
// you could overwrite the req properties
user?: { id: string };
// you can manually create on the server
id: string;
};
}
}
create a wrapper session function
export function withSession(handler: any) {
return withIronSessionApiRoute(handler, {
password: appConfig.secret,
cookieName: appConfig.cookies.sessionToken.name,
// Said in another way, the browser will not send a cookie with the secure attribute set over an unencrypted HTTP request
cookieOptions: appConfig.cookies.sessionToken.options,
})}
create the session object. you do not use getIronSession when creating a session.
you need that when you need to access to the session object in middleware
export default withSessio(
async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === "GET") {
try {
const sessionObj={....}
req.session.nameOfSessionObject={...sessionObj}
await req.session.save();
// whatever you want to return
return res.json(sessionObj);
} catch (error) {
console.error("error in verify post req", error);
// 422 Unprocessable Entity
res.status(422).send({ message: "Cannot create SESSION" });
}
} else if (req.method === "POST") {
try {
..HANDLE POST HERE
} catch (error) {
res.status(422).send({ message: "Cannot generate a SESSION" });
}
} else {
return res.status(200).json({ message: "Invalid api Route" });
}
}
);
now you can import above handler and connect with next-connect

Sveltekit typeError during post request with fetch

I got a nestjs application. It is listening on localhost:3000. I have health check, i can ping with curl or insomnia and it is working correctly. I can use localhost/3000/api/register to register a new user without any problem. I wanted to try it with sveltekit. And i had an issue when i tried to fetch data it and i got an error:
TypeError: fetch failed
at fetch (/Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/undici/index.js:105:13)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async send (/Users/marcelljuhasz/Development/svelte-kit-demo/src/lib/api.ts:16:13)
at async default (/Users/marcelljuhasz/Development/svelte-kit-demo/src/routes/register/+page.server.ts:23:15)
at async handle_action_json_request (file:///Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/#sveltejs/kit/src/runtime/server/page/actions.js:51:16)
at async resolve (file:///Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/#sveltejs/kit/src/runtime/server/index.js:356:17)
at async respond (file:///Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/#sveltejs/kit/src/runtime/server/index.js:229:20)
at async file:///Users/marcelljuhasz/Development/svelte-kit-demo/node_modules/#sveltejs/kit/src/exports/vite/dev/index.js:444:22
I checked my server i got the cors enabled. The front end is listening to: localhost:5173.
I have this code inside:
app.enableCors({
origin: 'http://localhost:5173',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
allowedHeaders: 'Content-Type, Accept',
preflightContinue: true,
});
I am learning sveltekit now and i checked a project inside the github repository to see how it is works but i read the documentations too:
https://github.com/sveltejs/realworld
The structure almost the same i have the lib folder with the api.ts
import { error } from '#sveltejs/kit';
const base = 'http://localhost:3000/api';
async function send({ method, path, data }) {
const opts = { method, headers: {} };
if (data) {
opts.headers['Content-Type'] = 'application/json';
opts.body = JSON.stringify(data);
}
const res = await fetch(`${base}/${path}`, opts);
if (res.ok || res.status === 422) {
const text = await res.text();
return text ? JSON.parse(text) : {};
}
console.log(res);
throw error(res.status);
}
export function get(path) {
return send({ method: 'GET', path });
}
export function del(path) {
return send({ method: 'DELETE', path,});
}
export function post(path, data) {
return send({ method: 'POST', path, data });
}
export function put(path, data) {
return send({ method: 'PUT', path, data });
}
I have a register.svelte in the routes dir. With +page.svelte and +page.server.ts is the same like in the repository, i just exclude my own fields. The data input is return in the correct format.
+page.server.ts looks like this, almost the same like in the repo:
import { fail, redirect } from '#sveltejs/kit';
import * as api from '$lib/api.js';
/** #type {import('./$types').PageServerLoad} */
export async function load({ parent }) {
const { user } = await parent();
if (user) throw redirect(307, '/');
}
/** #type {import('./$types').Actions} */
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const user = {
username: data.get('username'),
email: data.get('email'),
password: data.get('password')
};
const body = await api.post('register', { ...user });
if (body.errors) {
return fail(401, body);
}
console.log(body)
throw redirect(307, '/');
}
};
So in a nutshell i got typerror after i hit the sign uo button. On my server log tells nothing. I see this log in the sveltekit log. I tried to check cors, but it is okey and i haven't got any cors errors in dev console. I checked in my console with curl to check if is the server available. I tried to post, get with insomnia and curl. And it is worked as expected. I have no clue for this. It is wierd if i check the chrome dev tool the request. In the general tab the request url is: localhost:5173 which is the default vite.config for sveltekit server. But i passing my own server which is localhost:3000 and i dont understand what is this behavor. If anybody have experience with sveltekit i am curious what is wrong. I tried to fetch data with an own svelte file without +page.server.ts, i put this fetch method into the component file and it is worked. Wierd.

can i create a jwt with jwtwebtoken and then verify it with jose?

im using in my backend with jsonwebtoken to create jwt then i want in nextjs middleware in the front and to verify the token but jsonwebtoken not working in client render so can i use jose or any other library to verify this token or i must to create the token with jose also in the backend
import { jwtVerify } from "jose";
const secret: any = process.env.JWT_SECRET!;
export const config = {
matcher: ["/((?!api|_next|fonts|500|examples|[\\w-]+\\.\\w+).*)"],
};
export default function middleware(req: NextRequest) {
const url = req.url;
const token: any = req.cookies.get("token");
const urlToken = req.nextUrl.searchParams.get("token")!;
if (url.includes("/auth")) {
if (token === undefined) {
return NextResponse.next();
}
if (token)
try {
console.log(token);
const verify = jwtVerify(token, secret);
console.log(verify);
return NextResponse.redirect("http://localhost:3000/");
} catch (e) {
return NextResponse.next();
}
}
}

How do I stop auth middleware in NextJS applying to server-side/pre-rendering requests?

I'm trying to require the user to be logged in to access certain routes.
I have added the following middleware, as per the docs, but am having difficulty getting it to work.
I thought the issue was down to the server-side pre-rendered page always being created while unauthenticated, but I'm no longer sure. Middleware should only run on client-side requests from a browser, right?
When I include the block marked with 🟡s, the redirect does not happen.
When it's removed, the redirect always happens, even if the user is logged in.
Note that we're using "next": "^12.3.3", and we're not ready to upgrade to next v13 yet.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import * as auth from 'src/lib/auth';
const pathsNeedingAuth = [
'/dashboard$',
'/account/(password|reset|delete)$',
];
export default async function middleware(request: NextRequest) {
if (typeof window === 'undefined') { // 🟡
return NextResponse.next(); // 🟡
} // 🟡
const pathNeedsAuth = pathsNeedingAuth.some(
(path) => new RegExp(path).test(request.nextUrl.pathname),
);
if (!pathNeedsAuth) {
return NextResponse.next();
}
const isAuthed = await auth.isAuthenticated();
if (isAuthed) {
return NextResponse.next();
}
const url = request.nextUrl.clone();
url.searchParams.set('redirectUrl', url.pathname);
url.pathname = '/account/login';
return NextResponse.redirect(url);
}
export const config = {
matcher: [...pathsNeedingAuth],
};
Any help will be appreciated! I know the structure of the code is a little odd, but that's simply from changing it while trying various things.
Redirects are incompatible with next export.

res.redirect in API route NextJS gives a loop

I have this code in my /api/[verificationToken] which is when accessed by the user, the verification token will be updated. For now, I am trying to check if the token exists in the database and corresponds to a registered email.
import prisma from "../../../lib/prisma";
export default async function handler(req, res) {
const token = req.query;
const findEmail = await prisma.user.findFirst({
where: {
token: token.verificationToken,
},
});
if (findEmail) {
console.log("email exists");
} else {
console.log("email doesn't exist");
return res.redirect("/auth/login");
}
}
The problem is, when I go to http://localhost:3000/auth/api/nonexistenttoken, "email doesn't exist" displays in a loop. I have also tried
res.writeHead(302, {
Location: '/auth/login'
});
res.end();
But it still gives me the same loop. What I want to happen is that when the token doesn't exist (which also means the email also doesn't), it should redirect the user to the login page.

Resources