I creating a Next.js & Redux app. I using multiple middleware for redux. I have bindMiddleware function for checking development environment and apply development middlewares.
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunkMiddleware from "redux-thunk";
import promiseMiddleware from "redux-promise-middleware";
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== "production") {
const { composeWithDevTools } = require("redux-devtools-extension");
const loggerMiddleware = require("redux-logger");
return composeWithDevTools(
applyMiddleware([...middleware, loggerMiddleware])
);
}
return applyMiddleware(...middleware);
};
bindMiddleware([thunkMiddleware, promiseMiddleware])
My app not working because of this code:
return composeWithDevTools(
applyMiddleware([...middleware, loggerMiddleware])
);
If I use this code instead of the bad code, it won't give an error.
return composeWithDevTools(
applyMiddleware(...middleware)
);
!!! FIXED !!!
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== "production") {
const { composeWithDevTools } = require("redux-devtools-extension");
const { logger } = require("redux-logger"); // edited
return composeWithDevTools(applyMiddleware(...middleware, logger)); // edited
}
return applyMiddleware(...middleware);
};
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== "production") {
const { composeWithDevTools } = require("redux-devtools-extension");
const { logger } = require("redux-logger"); // edited
return composeWithDevTools(applyMiddleware(...middleware, logger)); // edited
}
return applyMiddleware(...middleware);
};
Related
Laravel in PHP made this easy with https://laravel.com/docs/9.x/session#flash-data, so I figured Next.js would have an easy way too.
I thought I'd be able to do something like:
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getSession(ctx);
if (!session) {
ctx.res.setHeader("yourFlashVariable", "yourFlashValue");
console.log('headers', ctx.res.getHeaders()); // Why is it not even appearing here?
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
const props = ...
return { props };
};
and then in my other page:
export const getServerSideProps: GetServerSideProps = async (context) => {
const { headers, rawHeaders } = context.req;
// look inside the headers for the variable
// ...
But the header doesn't appear.
If you know how to achieve the goal of a flash variable (even if not using headers), I'm interested in whatever approach.
(Originally I asked How can I show a toast notification when redirecting due to lack of session using Next-Auth in Next.js? but now feel like I should have asked this more generic question.)
UPDATE
I appreciate the reasonable suggestion from https://stackoverflow.com/a/72210574/470749 so have tried it.
Unfortunately, index.tsx still does not get any value from getFlash.
// getFlash.ts
import { Session } from 'next-session/lib/types';
export default function getFlash(session: Session) {
// If there's a flash message, transfer it to a context, then clear it.
const { flash = null } = session;
console.log({ flash });
// eslint-disable-next-line no-param-reassign
delete session.flash;
return flash;
}
// getNextSession.ts
import nextSession from 'next-session';
export default nextSession();
// foo.tsx
import { getSession } from 'next-auth/react';
import { GetServerSideProps, InferGetServerSidePropsType, NextApiRequest, NextApiResponse } from 'next';
import getNextSession from '../helpers/getNextSession';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getSession(ctx);
if (!session) {
const req = ctx.req as NextApiRequest;
const res = ctx.res as NextApiResponse;
const nSession = await getNextSession(req, res);
nSession.flash = 'You must be logged in to access this page.'; // THIS LINE CAUSES A WARNING
console.log({ nSession });
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
// ...
return { props };
};
// index.tsx
import { GetServerSideProps } from 'next';
import getFlash from '../helpers/getFlash';
import getNextSession from '../helpers/getNextSession';
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getNextSession(context.req, context.res);
let toast = getFlash(session);
console.log({ toast });
if (!toast) {
toast = 'no toast';
}
console.log({ toast });
return {
props: { toast }, // will be passed to the page component as props
};
};
Also, the nSession.flash = line causes this warning:
warn - You should not access 'res' after getServerSideProps resolves.
Read more: https://nextjs.org/docs/messages/gssp-no-mutating-res
Your first code is working fine for me (printing the headers in terminal). However, the combination will not work as intended because the headers you set in /foo (say) will be sent to browser, along with a status code of 307, and a location header of /. Now "the browser" will be redirecting to the location and it won't forward your headers. Similar threads: https://stackoverflow.com/a/30683594, https://stackoverflow.com/a/12883411.
To overcome this, you can do something like this. This works because the browser does send the cookies (in this case, set when you create a session).
// lib/session.ts
import type { IronSessionOptions } from 'iron-session'
import type { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
export const sessionOptions: IronSessionOptions = {
password: process.env.SECRET_COOKIE_PASSWORD as string,
cookieName: 'sid',
cookieOptions: { secure: process.env.NODE_ENV === 'production' },
}
declare module 'iron-session' {
interface IronSessionData {
flash?: string | undefined
}
}
export const withSessionRoute = (handler: NextApiHandler) =>
withIronSessionApiRoute(handler, sessionOptions)
export const withSessionSsr = <P extends Record<string, unknown> = Record<string, unknown>>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) => withIronSessionSsr(handler, sessionOptions)
// pages/protected.tsx
import type { NextPage } from 'next'
import { getSession } from 'next-auth/react'
import { withSessionSsr } from 'lib/session'
const ProtectedPage: NextPage = () => <h1>Protected Page</h1>
const getServerSideProps = withSessionSsr(async ({ req, res }) => {
const session = await getSession({ req })
if (!session) {
req.session.flash = 'You must be logged in to access this page.'
await req.session.save()
return { redirect: { destination: '/', permanent: false } }
}
return { props: {} }
})
export default ProtectedPage
export { getServerSideProps }
// pages/index.tsx
import type { InferGetServerSidePropsType, NextPage } from 'next'
import { withSessionSsr } from 'lib/session'
const IndexPage: NextPage<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ flash }) => {
// TODO: use `flash`
}
const getServerSideProps = withSessionSsr(async ({ req }) => {
// if there's a flash message, transfer
// it to a context, then clear it
// (extract this to a separate function for ease)
const { flash = null } = req.session
delete req.session.flash
await req.session.save()
return { props: { flash } }
})
export default IndexPage
export { getServerSideProps }
This also works if you want to set flash data in an API route instead of pages:
import { withSessionRoute } from 'lib/session'
const handler = withSessionRoute(async (req, res) => {
req.session.flash = 'Test'
await req.session.save()
res.redirect(307, '/')
})
export default handler
Complete example: https://github.com/brc-dd/next-flash/tree/with-iron-session
I'm relatively new to next-auth & next in general. We have an application which we are transitioning to using AWS Cognito for authentication. It's a server side rendered application & I know this as under pages/_app.tsx is getInitialProps(). The code being:
import React from "react";
import App from "next/app";
import Router from "next/router";
import { Provider } from "react-redux";
import withRedux from "next-redux-wrapper";
import ReduxToastr from "react-redux-toastr";
import NProgress from "nprogress";
import * as Sentry from "#sentry/node";
// import { setTokens } from "../util/tokens";
import jwtDecode from "jwt-decode";
import { SessionProvider, getSession } from "next-auth/react";
import createStore from "../redux/store";
import { setSession } from "../redux/session";
import "semantic-ui-css/semantic.min.css";
import "react-redux-toastr/lib/css/react-redux-toastr.min.css";
import "../styles/nprogress.css";
import "../styles/overrides.scss";
import "../styles/util.css";
import axios from "axios";
import { IdToken, CognitoTokens } from "../interfaces";
Router.events.on("routeChangeStart", () => {
NProgress.start();
});
Router.events.on("routeChangeComplete", () => NProgress.done());
Router.events.on("routeChangeError", () => NProgress.done());
NProgress.configure({ showSpinner: false });
type tProps = {
passport?: {};
};
class MyApp extends App {
static async getInitialProps({ Component, ctx }: any) {
debugger;
let pageProps: tProps = {};
const session = await getSession({
req: ctx.req,
}); // this calls '/api/auth/session'
// let tokens: AuthTokens = {};
if (ctx.isServer && ctx.query?.code) {
const {
AUTH_DOMAIN,
COGNITO_CLIENT_ID,
COGNITO_CLIENT_SECRET,
BASE_URL,
} = process.env;
const { data }: { data: CognitoTokens } = await axios.post(
`https://${AUTH_DOMAIN}/oauth2/token?client_id=${COGNITO_CLIENT_ID}&client_secret=${COGNITO_CLIENT_SECRET}&grant_type=authorization_code&redirect_uri=${BASE_URL}&code=${ctx.query.code}`,
);
}
return { pageProps };
}
render() {
debugger
const { Component, pageProps, store }: any = this.props;
return (
<SessionProvider session={pageProps.session}>
<Provider store={store}>
<Component {...pageProps} />
<ReduxToastr
preventDuplicates
position="bottom-right"
transitionIn="fadeIn"
transitionOut="fadeOut"
timeOut={5000}
progressBar
/>
</Provider>
</SessionProvider>
);
}
}
export default withRedux(createStore)(MyApp);
pages/api/auth/[...nextauth].ts
import NextAuth from "next-auth";
import CognitoProvider from "next-auth/providers/cognito";
export default NextAuth({
providers: [
CognitoProvider({
clientId: process.env.COGNITO_CLIENT_ID,
clientSecret: process.env.COGNITO_CLIENT_SECRET,
issuer: process.env.COGNITO_ISSUER,
}),
],
debug: process.env.NODE_ENV === "development" ? true : false,
});
There is authentication middleware defined for the API. The main reason for this is that we have a custom permissions attribute defined on a user in Cognito which defines what CRUD operations they can do in specific environments
server/index.ts
import express from "express";
import next from "next";
import bodyParser from "body-parser";
import session from "express-session";
import connectMongodbSession from "connect-mongodb-session";
// Config
import { /* auth0Strategy, */ getSessionConfig } from "./config";
const port = parseInt(process.env.PORT || "3000", 10);
const dev = process.env.NODE_ENV !== "production";
// API
import authAPI from "./api/auth";
import accountsAPI from "./api/accounts";
import usersAPI from "./api/users";
import jobsAPI from "./api/jobs";
import awsAPI from "./api/aws";
import completedJobsAPI from "./api/completedJobs";
// middleware
import { restrictAccess, checkPermissions } from "./middleware/auth";
// Server
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
const MongoDBStore = connectMongodbSession(session);
const store = new MongoDBStore({
uri: process.env.MONGO_PROD_URL,
collection: "godfatherSessions",
});
server.use(bodyParser.json());
server.use(session(getSessionConfig(store)));
server.get("/healthcheck", async (req: any, res, next) => {
const { db } = req.session.req.sessionStore;
try {
await db.command({
ping: 1,
});
res.status(200).end();
} catch (err) {
next(err);
}
});
// Middleware
server.use(checkPermissions);
// API
server.use(authAPI);
server.use(accountsAPI, restrictAccess);
server.use(usersAPI, restrictAccess);
server.use(jobsAPI, restrictAccess);
server.use(awsAPI, restrictAccess);
server.use(completedJobsAPI, restrictAccess);
// Protected Page Routes
server.get("/accounts", restrictAccess, (req: any, res) =>
app.render(req, res, "/accounts", req.query),
);
server.get("/users", restrictAccess, (req: any, res) =>
app.render(req, res, "/users", req.query),
);
server.get("/jobs", restrictAccess, (req: any, res) =>
app.render(req, res, "/jobs", req.query),
);
server.get("/completedJobs", restrictAccess, (req: any, res) => {
app.render(req, res, "/completedJobs", req.query);
});
server.get("/debug-sentry", function mainHandler() {
throw new Error("My first Sentry error!");
});
// Fallback Routes
server.all("*", handle as any);
// The error handler must be before any other error middleware and after all controllers
// server.use(Sentry.Handlers.errorHandler());
server.listen(port, (err?: Error) => {
if (err) throw err;
console.log(
`> Server listening at http://localhost:${port} as ${
dev ? "development" : process.env.NODE_ENV
}`,
);
});
});
server/middleware/auth.ts
/* eslint-disable #typescript-eslint/camelcase */
import "cross-fetch/polyfill";
import { Request, Response, NextFunction } from "express";
import { ForbiddenError } from "../../util/errors";
import { Environments } from "../../interfaces";
import jwt_decode from "jwt-decode";
import { CognitoUser, CognitoUserPool } from "amazon-cognito-identity-js";
import { getSession } from "next-auth/react";
export async function checkPermissions(
req: CustomRequest,
res: Response,
next: NextFunction,
) {
const { path, method } = req;
const { authorization, env } = req.headers;
const session = await getSession({ req });
debugger;
if (
!req.url.includes("/api/") ||
["/api/auth/callback/cognito", "/api/auth/session"].includes(req.path)
) {
return next();
}
if (req.headers.env === "development") {
return next();
}
console.log(session);
if (!authorization) {
return res.status(401).redirect("/login");
}
const decoded: any = jwt_decode(authorization);
const invalidAuth = isInvalidAuth(decoded, path);
if (!invalidAuth) {
const userPool = new CognitoUserPool({
UserPoolId: process.env.COGNITO_USER_POOL_ID,
ClientId: process.env.COGNITO_CLIENT_ID,
});
const username = decoded.username || decoded["cognito:username"];
const cognitoUser = new CognitoUser({
Username: username,
Pool: userPool,
});
const userAttributes: any = await getUserAttributes(cognitoUser);
const permissions = userAttributes.permissions
.split(",")
.map(perm => `"${perm}"`);
const hasPermission = getHasPermission(
method,
permissions,
env as Environments,
);
if (!hasPermission) {
return res
.status(403)
.send(
new ForbiddenError(
"You do not have permission to perform this action.",
).toErrorObject(),
);
}
return next();
}
return next(invalidAuth);
}
The callback URL defined in Cognito app client is "http://localhost:3000/api/auth/callback/cognito" (for development purposes - as is highlighted in the example).
When running the application & logging in via the Cognito hosted UI, the 1st breakpoint hit is the debugger on L5 in checkPermissions. The session is null at this point & the path is /api/auth/session.
The issue comes when I step through past that breakpoint to return next() where I get the error(s) shown in the picture below. So these are ECONNRESET errors & if I let it run freely, these errors occur over & over again resulting in an EMFILE error every now & again until eventually the application crashes. No other breakpoints are hit.
Versions:
next: v9.3.5
next-auth: v4.1.0
Even though, by default, next-auth will use localhost:3000, I kept getting an ENOTFOUND error when I didn't define the URL in the .env so I set it to:
NEXTAUTH_URL=http://127.0.0.1:3000
After reading all docs & examples & several tutorials on how to do it (no one has done a tutorial on v4 - just v3) I'm still stumped
For me helped to remove .next/ and add these lines to .env
NODE_TLS_REJECT_UNAUTHORIZED="0"
NEXTAUTH_URL="http://127.0.0.1:3000"
NEXTAUTH_URL_INTERNAL="http://127.0.0.1:3000"
It seems to work very fine.
i'm currently using NodeJS.
I'm trying to import a module to a component function and everything executes pretty well, but i still get this error in the server console:
error - src\modules\accountFunctions.js (15:35) # Object.User.GetData
TypeError: _cookieCutter.default.get is not a function
cookieCutter.get is actually a function and is working as inteneded
import cookieCutter from 'cookie-cutter'
import { useSelector, useDispatch } from 'react-redux'
import { useRouter } from 'next/router'
import { accountActions } from '../store/account'
const Auth = require('./auth.module')
const User = {}
User.GetData = async () => {
const route = useRouter()
const userData = useSelector((state) => state.user)
const dispatch = useDispatch()
const sessionId = cookieCutter.get('session')
if (sessionId && userData.username === '') {
const userExist = await Auth.loadUserInformation()
if (userExist.result === false) {
route.push('/login')
return false
}
dispatch(accountActions.updateAccountInformation(userExist.data))
return true
} else if (!sessionId) {
route.push('/login')
return false
}
}
module.exports = User
I know for a fact that a solution would be importing the library into the function compoenent but i really don't wanna keep on importing it everywhere.
This is how i'm importing the module.
import User from '../src/modules/accountFunctions'
const dashboard = () => {
console.log('Rance')
User.GetData()
return <NavBar />
}
export default dashboard
You need to move the cookie fetching logic to a useEffect inside the custom hook, so it only runs on the client-side. Calling cookieCutter.get won't work when Next.js pre-renders the page on the server.
const useUserData = async () => {
const route = useRouter()
const userData = useSelector((state) => state.user)
const dispatch = useDispatch()
useEffect(() => {
const getAuthenticatedUser = async () => {
const sessionId = cookieCutter.get('session')
if (sessionId && userData.username === '') {
const userExist = await Auth.loadUserInformation()
if (userExist.result === false) {
route.push('/login')
}
dispatch(accountActions.updateAccountInformation(userExist.data))
} else if (!sessionId) {
route.push('/login')
}
}
getAuthenticatedUser()
}, [])
}
I am using NextJS with next-i18next. This is my home page:
import {withTranslation} from '../config/next-i18next';
const Home = function Home() {
return (<div>test</div>)
};
Home.getInitialProps = async () => {
return {namespacesRequired: ['home']}
};
export default withTranslation('home')(Home);
What I want is to get current language inside a component/page, how can I do that ?
withTranslation injects the i18n object.
import {withTranslation} from '../config/next-i18next';
const Home = function Home({ i18n }) {
return (<div>{i18n.language}</div>)
// ----------------^
};
Home.getInitialProps = async () => {
return {namespacesRequired: ['home']}
};
export default withTranslation('home')(Home);
Or using Hooks,
import {useTranslation} from '../config/next-i18next';
const Home = function Home() {
const { i18n } = useTranslation('home');
return (<div>{i18n.language}</div>)
// ----------------^
};
Home.getInitialProps = async () => {
return {namespacesRequired: ['home']}
};
export default Home;
With Next.js you could also use the useRouter hook.
import {withTranslation} from '../config/next-i18next';
import { useRouter } from 'next/router'
const Home = function Home() {
const router = useRouter()
const currentLang = router.locale // => locale string eg. "en"
return (<div>test</div>)
};
Home.getInitialProps = async () => {
return {namespacesRequired: ['home']}
};
export default withTranslation('home')(Home);
When I compiled a typescript project I don't see express import import {Request, Response}. Now I am trying to use require but not sure how to extend the express object and use req and res. Any help will be appreciated.
index.ts
// import {Request, Response} from 'express';
import express = require('express');
import * as sdkStore from "../../common/sdk-store";
export function getInfo(req: Request, res: Response) {
var app = sdkStore.getSdkInstance(req.body.client);
app.Payment.Info(req.body, function (result) {
res.send(result);
});
}
I am using the same strategy to use Request & Response from express module.
This is how I am doing it (check parameters of getTemples method)-
Typescript
import * as express from "express";
import { Response as ApiResponse } from "../models/response.model";
import { TempleProvider } from "../providers/temple.provider";
export namespace TempleFacade {
export function getTemples(req: express.Request, res: express.Response, next: express.NextFunction): void {
let apiResponse: ApiResponse<any> = new ApiResponse();
TempleProvider.getTemples()
.then((response: Array<any>) => {
apiResponse.data = response;
res.json(apiResponse);
})
.catch((error: any) => {
apiResponse.data = null;
apiResponse.status = false;
apiResponse.messages = error;
res.json(apiResponse);
});
}
}
Compiled Javascript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const response_model_1 = require("../models/response.model");
const temple_provider_1 = require("../providers/temple.provider");
var TempleFacade;
(function (TempleFacade) {
function getTemples(req, res, next) {
let apiResponse = new response_model_1.Response();
temple_provider_1.TempleProvider.getTemples()
.then((response) => {
apiResponse.data = response;
res.json(apiResponse);
})
.catch((error) => {
apiResponse.data = null;
apiResponse.status = false;
apiResponse.messages = error;
res.json(apiResponse);
});
}
TempleFacade.getTemples = getTemples;
})(TempleFacade = exports.TempleFacade || (exports.TempleFacade = {}));
//# sourceMappingURL=temple.facade.js.map
I hope this helps :)