I'm building a nuxtjs app and try to set a cookie from a global middleware. I found this contribution on GitHub which shows a method to do this.
So I implemented my middleware like this
export default function ({ isServer, res, query }) {
if (query.lang) {
if (isServer) {
res.setHeader("Set Cookie", [`lang=${query.lang}`]);
} else {
document.cookie = `lang=${query.lang}`;
}
}
}
My problem is that when I visit my app with ?lang=xxx as a parameter, I'm always running into the else block of my if condition. So I get the error
document is not defined
Has anyone a idea what is wrong with my code. I can't see a difference to the code published on github.
You should use cookie-universal-nuxt.
Add this in your module section in nuxt.config.js:
['cookie-universal-nuxt', { alias: 'cookiz' }],
You can use it directly in the store with nuxtServerInit:
async nuxtServerInit({ commit, state, dispatch },
{ app, store, route, req, res, error, redirect }
) {
app.$cookiz.set('lang', route.query.lang)
})
Or in a middleware:
export default function ({ app, res, query }) {
if (query.lang) {
app.$cookiz.set('lang', query.lang)
}
}
You can using helpers from nuxt by using setCookie and custom middleware
https://nuxtjs.org/docs/configuration-glossary/configuration-servermiddleware/
middlewares/cookies.ts
export default function (req, res, next) {
let cookie = getCookie(req, '_id') || 'random_value'
setCookie(res, '_id', cookie)
// Don't forget to call next at the end if your middleware is not an endpoint
next()
}
Also update your nuxt.config.ts
export default defineNextConfig({
// ...
router: {
middleware: ["cookies"],
}
})
In Nuxt 3 and Nuxt 2 Bridge you can use useCookie
Nuxt provides an SSR-friendly composable to read and write cookies.
const lang = useCookie('lang')
lang.value = ''
export function getCookie(name, stringCookie) {
const matches = stringCookie.match(
new RegExp(
`(?:^|; )${name.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1')}=([^;]*)`,
),
);
return matches ? decodeURIComponent(matches[1]) : undefined;
}
Related
How can I trigger a redirect on the server side if a signed in user has not completed their profile page
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session || !ctx.session.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
// redirect to profile page if user has not completed profile
return next({
ctx: {
// infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
});
});
This is not currently possible in the way you are describing to the best of my knowledge.
Here are some alternatives that might be helpful:
In getServerSideProps
this only works if you want to redirect before the initial page load. You could also create a wrapper around gSSP to make this more DRY if you're going to use it on a lot of pages.
import { type GetServerSidePropsContext } from "next";
import { getServerAuthSession } from "../server/auth";
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
const session = await getServerAuthSession(ctx);
if (!session) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
return {
props: {},
};
}
export default function AuthedPage() {
return <div>Authed</div>;
}
As part of a query or mutation clientside
this is useful for a query or mutation that is only fired after the page has loaded. Again this is a very simple example and could be DRYed, probably the easiest way would be to extract into a custom hook.
import { useRouter } from "next/router";
import { api } from "../utils/api";
export default function AuthedPage() {
const router = useRouter();
// `authedHello` is the example Create T3 App "hello" procedure
// but as a protectedProcedure, ie throws "UNAUTHORIZED" if no session.
// Replace this with a middleware that throws on whatever condition you need it to.
const authedHello = api.example.protectedHello.useQuery(
{ text: "world" },
{
retry: (_count, err) => {
// `onError` only runs once React Query stops retrying
if (err.data?.code === "UNAUTHORIZED") {
return false;
}
return true;
},
onError: (err) => {
if (err.data?.code === "UNAUTHORIZED") {
void router.push("/");
}
},
}
);
return (
<div>
<h1>Authed Page</h1>
<p>{authedHello.data?.greeting}</p>
</div>
);
}
Using Next.js middleware
This is easy to apply to a bunch of routes using the matcher, but it falls a bit outside of T3 conventions.
// pages/middleware.ts
import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "../server/auth";
import type { NextApiRequest, NextApiResponse } from "next";
export async function middleware(req: NextApiRequest, res: NextApiResponse) {
const session = await getServerSession(req, res, authOptions);
if (!session?.user) {
return NextResponse.redirect(new URL("/", req.url));
}
}
export const config = {
matcher: ["/protectedPage", "/anotherProtectedPage"],
};
Using require in next-auth's useSession
this is useful if you want to guard a page but can't use getServerSideProps. It doesn't quite solve your specific problem, but might be useful to other people who find this. See: https://next-auth.js.org/getting-started/client#require-session
I have gone through the following tutorial to get my Next.js App integrated with Auth0.
I am able to log in and log out just fine but when trying to display user information on the page after login, the user object is unable to be returned. I have ensured that there is nothing wrong with the Profile.js page that is rendering the user object or the env.local file with my app's secret keys.
After further inspection I noticed that I get an error in the browser console that reads: Failed to Load Resource ... 404 Not Found: http://localhost:3000/api/auth/me.
This error gives me a gut feeling that there is a discrepancy in the mapping between my next.js app and Auth0 since I have modified the basepath in next.config.js:
module.exports = {
basePath: '/my_path',
webpack: (config) => {
return config
},
env: {
},
publicRuntimeConfig: {
BACKEND_API_URL: process.env.BACKEND_API_URL,
CONSENT_COOKIE_NAME: 'ConsentCookie'
},
}
Is there a way to add my basepath into the endpoint that the user object is being returned from? The end result would look something like: https://localhost:3000/my_path/api/auth/me
I am not 100% certain that this will fix my issue with getting the user object returned properly, so I am open to any other suggestions and willing to add more context surrounding specific files in my app.
Edit:
After bringing this issue up on the Auth0 forums (link), I was pointed towards this link, which is another example Next.js Auth0 sample app, except they have written their frontend with TypeScript (which I am not familiar with). They are manipulating the UserContext object and resetting the ProfileURL, which is what I am after; so what would be the JavaScript equivalent to this?
The same repsonse to the Auth0 forum post I mentioned also included another link to an example function that creates a custom URL for the login. This is very close to what I am after since again, I am trying to create a custom auth URL to retrieve the User object and get rid of the 404 ... /api/auth/me not found error.
Due to my inexperience with JS, my attempts at trying to create a similar function to the example stated previously have failed, so what would this look like?
I am feeling intense bittersweet emotions after finding an insultingly simple solution to this issue.
Found in the readme.md of the NextJS-Auth0 repository...
This small snippet of code fixed all of my issues after hours of searching for a solution -
// _app.js
function App({ Component, pageProps }) {
return (
<UserProvider loginUrl="/foo/api/auth/login" profileUrl="/foo/api/auth/me">
<Component {...pageProps} />
</UserProvider>
);
}
Now to get back to wiping the tears off my desk..
I have been having this issue too. What was happening for my Next app deployed on Vercel is that all the api/auth/* routes were not working in production but everything worked locally.
I'm using the Auth0 Universal Login Experience
// package.json
...
"dependencies": {
"#auth0/nextjs-auth0": "^1.9.2",
}
...
All I had before was the function
// api/auth/[...auth0].ts
import { handleAuth } from "#auth0/nextjs-auth0";
export default handleAuth();
So what I did is create all the paths I'd need in my application in their respective files. I think Next.js was not creating the dynamic files at [...auth0].ts
// api/auth/callback.ts
import { handleCallback } from "#auth0/nextjs-auth0";
import { NextApiRequest, NextApiResponse } from "next";
const callbackHandler = async (req: NextApiRequest, res: NextApiResponse) => {
try {
await handleCallback(req, res);
} catch (error) {
res.status(error.status || 400).end(error.message);
}
};
export default callbackHandler;
// api/auth/login.ts
import { handleLogin } from "#auth0/nextjs-auth0";
import { NextApiRequest, NextApiResponse } from "next";
const loginHandler = async (req: NextApiRequest, res: NextApiResponse) => {
try {
await handleLogin(req, res, {
authorizationParams: {
screen_hint: "login",
},
});
} catch (error) {
res.status(error.status || 400).end(error.message);
}
};
export default loginHandler;
// api/auth/logout.ts
import { handleLogout } from "#auth0/nextjs-auth0";
import { NextApiRequest, NextApiResponse } from "next";
const logoutHandler = async (req: NextApiRequest, res: NextApiResponse) => {
try {
await handleLogout(req, res);
} catch (error) {
res.status(error.status || 400).end(error.message);
}
};
export default logoutHandler;
// api/auth/me.ts
// not api/auth/profile.ts
import { handleProfile } from "#auth0/nextjs-auth0";
import { NextApiRequest, NextApiResponse } from "next";
const profileHandler = async (req: NextApiRequest, res: NextApiResponse) => {
try {
await handleProfile(req, res);
} catch (error) {
res.status(error.status || 400).end(error.message);
}
};
export default profileHandler;
// api/auth/signup.ts
import { handleLogin } from "#auth0/nextjs-auth0";
import { NextApiRequest, NextApiResponse } from "next";
const signupHandler = async (req: NextApiRequest, res: NextApiResponse) => {
try {
await handleLogin(req, res, {
authorizationParams: {
screen_hint: "signup",
},
});
} catch (error) {
res.status(error.status || 400).end(error.message);
}
};
export default signupHandler;
I need to redirect visitors from /Contact to /contact.
When I am doing it as described in the docs I get an infinite redirect loop.
This is what I tried:
// next.config.js
async redirects() {
return [
{
source: '/Contact',
destination: '/contact',
permanent: true
}
]
}
With Next.JS >=12 you can use custom middleware.
Create file named middleware.js under root folder and use this code:
import { NextResponse } from 'next/server';
const Middleware = (req) => {
if (req.nextUrl.pathname === req.nextUrl.pathname.toLowerCase())
return NextResponse.next();
return NextResponse.redirect(new URL(req.nextUrl.origin + req.nextUrl.pathname.toLowerCase()));
};
export default Middleware;
The following will redirect all paths that don't exist to a lower case path. If the path is not found, it will show a 404.
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import Error from 'next/error'
export default function ResolveRoute() {
const router = useRouter()
useEffect(() => {
const { pathname } = router;
if (pathname !== pathname.toLowerCase()) {
router.push(pathname.toLowerCase())
}
},[router])
return <Error statusCode={404} />
}
Putting this file name as "[route].js" in the root of your pages folder will act as a catch all (unless the page exists). This will redirect to lower case. If already ready lower case then it means the page is a 404.
Following this suggestion from Reddit https://www.reddit.com/r/nextjs/comments/hk2qmk/nextjs_routing_case_sensitivity_issue/fwt29n2?utm_source=share&utm_medium=web2x&context=3
I fixed it using the _error.js page component. Like this:
import { hasUpperCase } from '../lib/string';
...
Error.getInitialProps = ({ asPath, err, res }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
if (asPath && hasUpperCase(asPath)) {
res.writeHead(307, { Location: asPath.toLowerCase() });
res.end();
}
return { statusCode };
};
export default Error;
I would also prefer doing this with redirects like your example though.
I found this helpful link that cover many solution for this issue:
https://www.youtube.com/watch?v=_vSMITiXAik
Solution 1:
use rewirte function in your next.config.js
return [
{
source: "(c|C)(o|O)(n|N)(t|T)(a|A)(c|C)(t|T)",
destination: "/Contact", // here you need to add the exact page contact or Contact
},
];
},
use the _middleware feature:
inside you pages folder add a _middleware.ts file.
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname === request.nextUrl.pathname.toLocaleLowerCase())
return NextResponse.next();
return NextResponse.redirect(
`${request.nextUrl.origin}${request.nextUrl.pathname.toLocaleLowerCase()}`
);
}
with this solution you can need to rename your page to be lowercase.
use middleware feature with a folder for each page.
you need to keep the _middleware.ts the same and you need to do those steps:
create a folder contact all lowercase.
move your page inside this folder.
add an index.ts that point to your page.
its conent should be something like this:
export { default } from "./contact";
rename all your page with this extension: .page.tsx or page.ts if you use typescript and .page.jsx or page.js if javascript.
in next.config.js you need to add this pageExtensions: ["page.tsx", "page.ts"] if you use typescript and pageExtensions: ["page.jsx", "page.js"] if you use javascript.
you need to do this for all your pages.
Here's a very elegant solution that will also carry over any search/query params:
import { NextResponse } from 'next/server';
const Middleware = (req) => {
if (req.nextUrl.pathname !== req.nextUrl.pathname.toLowerCase()) {
const url = req.nextUrl.clone()
url.pathname = url.pathname.toLowerCase()
return NextResponse.redirect(url)
}
return NextResponse.next();
};
export default Middleware;
When I ran into this problem, I noticed NextJS appends a trailing slash to the paths. When I looked at my network console in the browser, I saw requests with alternating status codes of 307 and 308. Once I added a trailing slash to the destination path, I saw a lot of requests with a status of 307, but still getting errors.
Once I added a trailing slash to both the source and destination paths, the redirect happened as expected:
// next.config.js
async redirects() {
return [
{
source: '/Contact/',
destination: '/contact/',
permanent: true
}
]
}
From nuxt auth website I saw this:
setUserToken(token)
Returns: Promise
Set the auth token and fetch the user using the new token and current strategy.
TIP: This function can properly set the user after registration
this.$auth.setUserToken(token)
.then(() => this.$toast.success('User set!'))
Tried to use it and it said method is undefined, looked up in the source files and none of methods are like this one.
I am not very good with this but, how would I set user and token with nuxt/auth module after registration or anything but login/loginWith?
If there is no option for that why is it there on documentation?
I would also need to know if I need to create custom auth do I need to use both cookies and localstorage or just one of them?
It says that cookies are used for server side and storage for client side.
Can I use just cookies and on nuxtServerInit get cookie for token and set token and user data fetched by api within vuex store? Then use it from there if it is needed?
Nuxt/auth module hurt my brain so long and today I created custom module:
First I have this store structure:
store/
-- index.js
-- mutations.js
-- actions.js
-- state.js
-- getters.js
middleware/
-- redirectIfAuth.js
-- redirectIfNotAuth.js
layouts/
default.vue -> has redirectIfNotAuth.js
guest.vue -> has redirectIfAuth.js
pages/
-- login/
---- index.vue -> uses guest.vue as layout
-- dashboard/
----- index.vue -> uses default.vue as layout without declaration
Inside Index.js I have:
import state from './state'
import * as actions from './actions'
import * as mutations from './mutations'
import * as getters from './getters'
export default {
state,
getters,
mutations,
actions,
modules: {}
}
Inside State.js I have:
export default () => ({
user: null,
token: null,
headers: null
})
Inside Actions.js I have:
const cookieparser = process.server ? require('cookieparser') : undefined
// importing server based cookie library
export async function nuxtServerInit ({ commit }, { req, res }) {
// If we have any axios requests we need to add async/await
// And since this works on server mode, we don't need to check is it server
let token = null
if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie)
try {
token = parsed.authToken
} catch (e) {
console.log(e)
}
}
// If we have token within cookies we get user data from api and we pass Autorization headers with token
if (token !== null && token !== false) {
await axios.get('/api/auth/me', {
headers: {
'Authorization': token
}
}).then((response) => {
// If we get user data we set it to store
commit('setUser', response.data.data)
commit('setToken', token)
commit('setHeaders', token)
}).catch((error) => {
// If we get error, we should logout user by removing data within cookies and store
// Additionally you can create specific code error on backend to check if token is expired or invalid
// and then check for status code and then remove data
commit('setUser', null)
commit('setToken', null)
res.setHeader('Set-Cookie', [`authToken=false; expires=Thu, 01 Jan 1970 00:00:00 GMT`])
// This is only way I found useful for removing cookies from node server
console.warn(error)
})
}
}
Inside Mutations.js I have:
export const setToken = (state, payload) => state.token = payload
export const setUser = (state, payload) => state.user = payload
export const setHeaders = (state, payload) => {
state.headers = {
headers: {
'Authorization': payload
}
}
}
Inside Getters.js I have:
export const getUser = (state) => state.user
export const getToken = (state) => state.token
export const getHeaders = (state) => state.headers
Second I created two middlewares and it seems like nuxt middlewares work on both server and client sides, so I needed to require both libraries for server and client side Then I checked which side it is and then try to get token for further investigations If you include and don't check for server and client but use one of them, your templates wont render but show undefined errors for req on client instead and on server it wont show anything.
Inside redirectIfAuth.js I have:
const cookieparser = process.server ? require('cookieparser') : undefined
const Cookie = process.client ? require('js-cookie') : undefined
export default function ({ app, redirect, req }) {
let token = null
if (process.server) {
if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie)
try {
token = parsed.authToken
} catch (e) {
console.log(e)
}
}
} else if (process.client) {
token = Cookie.get('authToken')
}
if (token && token !== false) {
app.store.commit('setToken', token)
app.store.commit('setHeaders', token)
if (app.store.state.user) {
if (app.store.state.user.roles.includes('customer')) {
return redirect({
name: 'customer-slug',
params: { slug: app.store.state.user.username }
})
} else if (app.store.state.user.roles.includes('admin')) {
return redirect({
name: 'dashboard'
})
} else {
return redirect({
name: 'index'
})
}
} else {
return redirect({
name: 'index'
})
}
}
}
Inside redirectIfNotAuth.js I have:
const cookieparser = process.server ? require('cookieparser') : undefined
const Cookie = process.client ? require('js-cookie') : undefined
export default function ({ app, redirect, route, req }) {
let token = null
if (process.server) {
if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie)
try {
token = parsed.authToken
} catch (e) {
console.log(e)
}
}
} else if (process.client) {
token = Cookie.get('authToken')
}
if (token === null || token === false) {
return redirect({
name: 'login',
query: {
redirect: route.fullPath
}
})
}
}
Now we use these middlewares within pages or layouts as:
export default {
middleware: ['redirectIfAuth']
}
Or
export default {
middleware: ['redirectIfNotAuth']
}
Login:
async login () {
if (this.form.email !== '' && this.form.password !== '') {
await this.$axios.post('/api/auth/login', this.form).then((response) => {
this.$store.commit('setUser', response.data.data)
this.$store.commit('setToken', 'Bearer ' + response.data.meta.access_token)
this.$store.commit('setHeaders', 'Bearer ' + response.data.meta.access_token)
Cookie.set('authToken', 'Bearer ' + response.data.meta.access_token, { expires: 365 })
// Cookie.set('authUser', response.data.data, { expires: 365 }) if you need user data within cookies
if (this.$route.query.redirect) {
this.$router.push(this.$route.query.redirect)
}
this.$router.push('/')
})
}
}
Logout:
async logout () {
await this.$axios.post('/api/auth/logout', {}, this.headers)
// Cookie.remove('authUser') if exists
Cookie.remove('authToken')
this.$router.push('/')
}
I hope this helps someone or someone get idea from this to make something else. I had million problems with official nuxt auth and only this helped me sort things out...
In my Node/Express API in order to protect my routes I am passport.authenticate
app.get('/', passport.authenticate('jwt', { session: false}), (req, res) => { //whatever })
I have additional middleware on these routes and it gets rather long and I did not want to have to require passport into all my router files so inside a seperate file I created another method on an existing auth object called protect
const auth = {
protect() {}
}
This allows me to do this
app.get('/', auth.protect, (req, res) => { //whatever })
Initially I tried to return the passport.authenticate line
const auth = {
protect() {
return passport.authenticate('jwt', { session: false})
}
}
But this doesnt work
I then wrapped it inside of an arrow function
const auth = {
protect() {
() => passport.authenticate('jwt', { session: false})
}
}
Now when I call auth.protect on my routes it works just fine.
Additionally if I just export an arrow function
export const protect = () => {
return passport.authenticate('jwt', { session: false})
}
This also works when I call protect on my routes.
Why do I need to return an arrow function with the code I want to run, why doesnt the first example with the return not work when I call the function on my route?
The first example seems to return the result of the function execution, the second one returns a function.
Wktrf is on the money. To elaborate: app.get() requires two arguments, a path and a call back function.
http://expressjs.com/en/api.html#app.get
const auth = {
protect() {
return passport.authenticate('jwt', { session: false})
}
}
The above returns the result of passport.auth. However a function is required as an argument for app.get().
const auth = {
protect() {
() => passport.authenticate('jwt', { session: false})
}
}
Your second example returns a function: () => passport.authenticate('jwt', { session: false})
Which app.get() can then work with.
Hope this helps.