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")
}
}
}
Related
Tell me I want to make middleware to protect the administrator pages, I have authorization through sessions on the project. The project itself is on next js . I want to use React Query to protect pages, but I get the error: An error has occurred: Unexpected token < in JSON at position 0 react query
APi:
import type { NextApiRequest, NextApiResponse } from 'next'
import { route } from 'next/dist/server/router'
import { useRouter } from 'next/router'
import checkSession from '../../src/services/checkCookie'
export async function middleware(req: NextApiRequest,res:NextApiResponse) {
if (req.method === 'GET') {
try {
const router= useRouter()
const sid = req.cookies['sid']
const admin = await checkSession(sid)
console.log(router.pathname)
// if (router.pathname === '/admin/login' || router.pathname === '/admin/regAdmin' || admin) {
// return res.next()
// }
res.send(admin)
const host = process.env.NODE_ENV === 'production' ? process.env.HOST : 'http://localhost:3000'
// return res.redirect(host + '/admin/login')
return res.send({ redirectUrl: '/admin/login' })
}catch (error) {
console.error(error)
res.status(500).send({ message: "Server error" })
}
}else{
res.status(404).send({ message: "adress error" })
}
}
Service in api (checkSessin) :
export default async function checkSession (token: string) {
// const token = req.cookies['sid']
if (typeof window === 'undefined' && token) {
const unsign = (await import('./signature')).unsign
const sessionToken = unsign(token, process.env.SECRET!)
if (sessionToken && typeof sessionToken === 'string') {
const db = (await import('../../prisma')).default
const session = db.session.findUnique({ where: { sessionToken },
include: { admin: true } })
if (session) {
return { admin: session.admin }
}
}
}
}
page admin :
import { NextPage } from "next"
import AdminLayout from "../../src/component/admin/AdminLayout"
import { SalesAdminComponent } from "../../src/component/admin/SalesAdmin"
import { useQuery } from 'react-query'
const AdminTable: NextPage = () => {
const { isLoading, error, data,isSuccess} = useQuery('sid', () =>
fetch('api/checkSession',{
method:'GET',
headers: {
"Content-Type": "application/json"
}
}).then(res =>res.json())
)
if (isLoading) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<>
{isSuccess &&
<AdminLayout title="OPEL Admin">
<SalesAdminComponent />
</AdminLayout>
}
{isLoading && <p>Loading..</p>}
{error && <p>Error occurred!</p>}
</>
)
}
export default AdminTable
Lots of things are going on here. The response is probably an HTML page that cannot be parsed to JSON. Please include logs both from the server and the browser.
Also, why are you using the client router on the server?
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.
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 }
I am trying to build a react app using relay following instructions from the react-relay step by step guide. In the guide the auth token is stored in the env file, and I am trying to retrieve my token from memory which is created when the user logs in and is passed down to all components using the context API. I am not storing it in local storage and have a refresh token to automatically refresh the JWT.
From the tutorial, the relay environment class is not a React component because of which I cannot access the context object.
Is there a way to pass the token from my context to the relay environment class or any middleware implementation to accomplish this.
Any help is greatly appreciated.
import { useContext } from 'react';
import { Environment, Network, RecordSource, Store } from 'relay-runtime';
import axios from "axios";
import { AppConstants } from './app-constants';
import { AuthContext, AuthSteps } from "./context/auth-context";
import { useCookie } from './hooks/useCookie';
interface IGraphQLResponse {
data: any;
errors: any;
}
async function fetchRelay(params: { text: any; name: any; }, variables: any, _cacheConfig: any) {
const authContext = useContext(AuthContext); //Error - cannot access context
const { getCookie } = useCookie(); //Error - cannot access context
axios.interceptors.request.use(
(config) => {
const accessToken = authContext && authContext.state && authContext.state.token;
if(accessToken) config.headers.Authorization = `Bearer ${accessToken}`;
return config;
},
(error) => {
Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const originalRequest = error.config;
const refreshToken = getCookie(AppConstants.AUTH_COOKIE_NAME);
if(refreshToken && error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const response = await axios
.post(process.env.REACT_APP_REFRESH_TOKEN_API_URL!, { refreshToken: refreshToken });
if (response.status === 200 && response.data && response.data.accessToken) {
authContext && authContext.dispatch && authContext.dispatch({
payload: {
token: response.data.accessToken
},
type: AuthSteps.SIGN_IN
});
accessToken = response.data.accessToken;
return axios(originalRequest);
}
}
return Promise.reject(error);
}
);
const data: IGraphQLResponse = await axios.post(process.env.REACT_APP_GRAPHQL_URL!, {
query: params.text,
variables
});
if(Array.isArray(data.errors)) {
throw new Error(
`Error fetching GraphQL query '${
params.name
}' with variables '${JSON.stringify(variables)}': ${JSON.stringify(
data.errors,
)}`,
);
}
return data;
}
export default new Environment({
network: Network.create(fetchRelay),
store: new Store(new RecordSource(), {
gcReleaseBufferSize: 10,
}),
});
I’m getting an error on my Gatsby+Auth0 app when trying to log in. Here is the error:
TypeError: Cannot read property 'idTokenPayload' of undefined
(anonymous function)
src/utils/auth.js:1
> 1 | import auth0 from "auth0-js"
2 | import { navigate } from "gatsby"
3 |
4 | const isBrowser = typeof window !== "undefined"
When I log in to my app it works the first time and I can see that authResult is console logging proper values, but the moment I click on any routes or refresh the page, it returns this error. I was working fine until this morning, I didn’t change anything other than some CSS styles.
Here is my auth.js file:
import auth0 from "auth0-js"
import { navigate } from "gatsby"
const isBrowser = typeof window !== "undefined"
const auth = isBrowser
? new auth0.WebAuth({
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENTID,
redirectUri: process.env.AUTH0_CALLBACK,
responseType: "token id_token",
scope: "openid profile email",
})
: {}
const tokens = {
accessToken: false,
idToken: false,
expiresAt: false,
}
let user = {}
export const isAuthenticated = () => {
if (!isBrowser) {
return
}
return localStorage.getItem("isLoggedIn") === "true"
}
export const login = () => {
if (!isBrowser) {
return
}
auth.authorize()
}
const setSession = (cb = () => {}) => (err, authResult) => {
console.log(authResult);
localStorage.setItem("userAuthID", authResult.idTokenPayload.sub)
localStorage.setItem("userIdToken", 'Bearer '+authResult.idToken)
if (err) {
navigate("/")
cb()
return
}
if (authResult && authResult.accessToken && authResult.idToken) {
let expiresAt = authResult.expiresIn * 1000 + new Date().getTime()
tokens.accessToken = authResult.accessToken
tokens.idToken = authResult.idToken
tokens.expiresAt = expiresAt
user = authResult.idTokenPayload
localStorage.setItem("isLoggedIn", true)
navigate("/account")
cb()
}
}
export const silentAuth = callback => {
if (!isAuthenticated()) return callback()
auth.checkSession({}, setSession(callback))
}
export const handleAuthentication = () => {
if (!isBrowser) {
return
}
auth.parseHash(setSession())
}
export const getProfile = () => {
return user
}
export const logout = () => {
localStorage.setItem("isLoggedIn", false)
localStorage.removeItem("userIdToken");
auth.logout()
}
And here is my callback.js file:
import React, { Component } from 'react'
import { handleAuthentication } from "../utils/auth"
import { navigate } from 'gatsby';
export default class callback extends Component {
componentDidMount() {
handleAuthentication();
setTimeout(() => {
navigate('/account')
}, 1500);
}
render() {
return (
<div>
<div className="hero is-white is-fullheight">
<div className="hero-body">
<div className="container">
<h1 className="title is-1 is-spaced has-text-centered logo-text">Fottom</h1>
</div>
</div>
</div>
</div>
)
}
}
Can some please help me find what is missing here? Thanks!
Your problem here is that you are trying to access the property idTokenPayload of authResult, without checking before if authResult is null. In your setSession() function, in case of error, you are accessing authResult. You should only set the localStorage items when the login was successful.
Try this code:
const setSession = (cb = () => {}) => (err, authResult) => {
console.log(authResult);
if (err) {
navigate("/")
cb()
return
}
if (authResult && authResult.accessToken && authResult.idToken) {
localStorage.setItem("userAuthID", authResult.idTokenPayload.sub) // moved after error control
localStorage.setItem("userIdToken", 'Bearer '+authResult.idToken) // moved after error control
let expiresAt = authResult.expiresIn * 1000 + new Date().getTime()
tokens.accessToken = authResult.accessToken
tokens.idToken = authResult.idToken
tokens.expiresAt = expiresAt
user = authResult.idTokenPayload
localStorage.setItem("isLoggedIn", true)
navigate("/account")
cb()
}
}
I agree with #mhSangar that you should probably only attempt to access the payload AFTER ensuring that it's not null.
But I think there's also another layer to the issue that you're experiencing. You mentioned that everything works fine until you refresh, which I'm assuming is when the token refresh is attempted. My guess is that the issue lies within the checkSession method.
Currently, it's being invoked like this:
auth.checkSession({}, setSession(callback))
You have to pass a config as the first argument to that method (not just an empty object). When I invoke it in my app, I pass the following args:
{
audience,
callbackURL,
clientID,
domain
}
Try passing those arguments to auth.checkSession and see if that fixes it.
i.e.
export const silentAuth = callback => {
if (!isAuthenticated()) return callback()
auth.checkSession(
{
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENTID,
redirectUri: process.env.AUTH0_CALLBACK,
audience: YOUR_AUDIENCE (looks like AUTH0_DOMAIN/api/v2/)
},
setSession(callback)
)
}