Closed kripod closed 2 years ago
@kripod So I think I have found a very neat solution/pattern for this, let me know what you think!
// pages/admin.jsx
export default function AdminDashboard () {
const [session] = useSession()
// session is always non-null inside this page, all the way down the React tree.
return "Some super secret dashboard"
}
AdminDashboard.auth = true
//pages/_app.jsx
export default function App({ Component, pageProps }) {
return (
<SessionProvider session={pageProps.session}>
{Component.auth
? <Auth><Component {...pageProps} /></Auth>
: <Component {...pageProps} />
}
</SessionProvider>
)
}
function Auth({ children }) {
const [session, loading] = useSession()
const isUser = !!session?.user
React.useEffect(() => {
if (loading) return // Do nothing while loading
if (!isUser) signIn() // If not authenticated, force log in
}, [isUser, loading])
if (isUser) {
return children
}
// Session is being fetched, or no user.
// If no user, useEffect() will redirect.
return <div>Loading...</div>
}
It can be easily be extended/modified to support something like an options object for role based authentication on pages. An example:
// pages/admin.jsx
AdminDashboard.auth = {
role: "admin",
loading: <AdminLoadingSkeleton/>,
unauthorized: "/login-with-different-user" // redirect to this url
}
Because of how _app
is done, it won't unnecessarily contant the /api/auth/session
endpoint for pages that do not require auth.
Whoa, looks like a well-thought pattern with a lot of convenience features included!
I like that. My only concern is about type safety for:
@kripod So I think I have found a very neat solution/pattern for this, let me know what you think!
// pages/admin.jsx export default function AdminDashboard () { const [session] = useSession() // session is always non-null inside this page, all the way down the React tree. return "Some super secret dashboard" } AdminDashboard.auth = true
//pages/_app.jsx export default function App({ Component, pageProps }) { return ( <SessionProvider session={pageProps.session}> {Component.auth ? <Auth><Component {...pageProps} /></Auth> : <Component {...pageProps} /> } </SessionProvider> ) } function Auth({ children }) { const [session, loading] = useSession() const isUser = !!session?.user React.useEffect(() => { if (loading) return // Do nothing while loading if (!isUser) signIn() // If not authenticated, force log in }, [isUser, loading]) if (isUser) { return children } // Session is being fetched, or no user. // If no user, useEffect() will redirect. return <div>Loading...</div> }
It can be easily be extended/modified to support something like an options object. An example:
// pages/admin.jsx AdminDashboard.auth = { role: "admin", loading: <AdminLoadingSkeleton/>, unauthorized: "/login-with-different-user" // redirect to this url }
Because of how
_app
is done, it won't unnecessarily contant the/api/auth/session
endpoint for pages that do not require auth.
Just beautiful approach <3 nice work!
@kripod Ok you've got me, needed to try this out before going to bed ^^
Here's my TypeScript variant, with added types here and there:
In auth.utils.ts:
/**
* Authentication configuration
*/
export interface AuthEnabledComponentConfig {
authenticationEnabled: boolean;
}
/**
* A component with authentication configuration
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ComponentWithAuth<PropsType = any> = React.FC<PropsType> &
AuthEnabledComponentConfig;
In _app.tsx:
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/ban-types
type NextComponentWithAuth = NextComponentType<NextPageContext, any, {}> &
Partial<AuthEnabledComponentConfig>;
In whatever page:
import React from 'react';
type FooProps = { }
const FooPage: ComponentWithAuth<FooProps> = (props: FooProps) => {
...
return <foo />;
}
FooPage.authenticationEnabled = true;
export default FooPage;
This seems to be working and feels relatively inoffensive/safe. The _app.tsx file uses Partial
just to err on the safe side. No hard contract there, but at least it provides some structure.
@balazsorban44 I see SessionProvider here, but the docs say import { Provider } from 'next-auth/client' for using it in _app. Is this a forthcoming change or are the docs out of alignment?
//pages/_app.jsx export default function App({ Component, pageProps }) { return ( <SessionProvider session={pageProps.session}> {Component.auth ? <Auth><Component {...pageProps} /></Auth> : <Component {...pageProps} /> } </SessionProvider> ) }
it's just a convention I had in our app. You'll potentially end up with many Providers, so I just renamed it to better align with its purpose.
I got a little bug with this approach I got redirected when I put some query in the URL like localhost:3000/?id=1 or in dynamic routes when I refresh the page, it's just me? @balazsorban44 I tested with 2 different apps and the same thing happen :(
Hard to tell. It's not really a feature of next-auth, but something I implemented in user land. Therefore I don't think I can help resolve it here. Could you open a discussion, and add a reproduction?
This is exactly what I've been looking for! Brilliant stuff. Good job @balazsorban44 ! Really helpful for those of us (me) new to NextJS let alone NextAuth :)
Folks using react-query
, check out https://github.com/nextauthjs/react-query
in this example how to use unauthorized
in _app.js
@zeing if you refer to my suggestion, you will need to extend the logic here:
{Component.auth
? <Auth><Component {...pageProps} /></Auth>
: <Component {...pageProps} />
}
As Compnent.auth
will not be a simple boolean anymore, but an object.
@zeing if you refer to my suggestion, you will need to extend the logic here:
{Component.auth ? <Auth><Component {...pageProps} /></Auth> : <Component {...pageProps} /> }
As
Compnent.auth
will not be a simple boolean anymore, but an object.
I try to think about How to redirect or use useRouter in _appjs
?
In my case i''m using useEffect
=> useRouter
& getInitialProps
=> res.writeHead
to redirect user to login page. So finally now, i can prevent user from opening Authenticated page both server side & client side. Thank's @balazsorban44 👍
@zeing doesn't have to be in the _app component's body, you could extract it to its own component. besides, useRouter should work in _app anyway.
@wachidmudi glad it works for you! 🙂
Hi Guys,
Thank you @balazsorban44 for this pretty solution.
In case someone would like to have a server side redirect solution (eg. 307 HTTP Response) and session obtained on server side - here's mine solution (may require further improvements):
export const withAuthenticatedOrRedirect = async (context: NextPageContext, destination: string = '/', fn?: (context: NextPageContext) => object) => {
const session = await getSession(context);
const isUser = !!session?.user;
// No authenticated session
if(!isUser) {
return {
redirect: {
permanent: false,
destination
}
};
}
// Returned by default, when `fn` is undefined
const defaultResponse = { props: { session } };
return fn ? { ...defaultResponse, ...fn(context) } : defaultResponse;
}
Destination is custom user given, but can be modified to automatically go to sign in & redirect back url.
Usage with only auth protection:
export const getServerSideProps = (context: NextPageContext) => withAuthenticatedOrRedirect(context, '/auth/login')
Usage with additional user given serverSideProps:
export const getServerSideProps = (context: NextPageContext) => withAuthenticatedOrRedirect(context, '/auth/login', (context: NextPageContext) => {
return {
props: {}
}
})
Best Regards
As I am too lazy to add .auth
on every protected page, I did it by pathname
.
Auth
component is the same. requireAuth
is pretty much the thing I added.
const RESTRICTED_PATHS = ["/restricted"]
...
...
const requireAuth = RESTRICTED_PATHS.some((path) => router.pathname.startsWith(path))
This way, under /restricted
, any pages will require a sign-in.
import { Provider, signIn, useSession } from "next-auth/client"
import { AppProps } from "next/app"
import React from "react"
interface Props {}
const Auth: React.FC<Props> = ({ children }) => {
const [session, loading] = useSession()
const isUser = !!session?.user
React.useEffect(() => {
if (loading) return // Do nothing while loading
if (!isUser) signIn() // If not authenticated, force log in
}, [isUser, loading])
if (isUser) {
return <>{children}</>
}
// Session is being fetched, or no user.
// If no user, useEffect() will redirect.
return <div>Loading...</div>
}
const RESTRICTED_PATHS = ["/restricted"]
const MyApp: React.FC<AppProps> = ({ Component, pageProps, router: { route } }) => {
const requireAuth = RESTRICTED_PATHS.some((path) => route.startsWith(path))
return (
<Provider session={pageProps.session}>
{requireAuth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
)}
</Provider>
)
}
export default MyApp
A nice summary of my approach from https://github.com/nextauthjs/next-auth/issues/1210#issuecomment-782630909 in an article form can be found here: https://simplernerd.com/next-auth-global-session
@kripod I still think your idea is valid, and I am working on a useSession({ required: true })
API change over at #2236.
From now on, let us keep the discussion related to the OPs problem, as my suggestion is a workaround for this issue, rather than an actual solution. 😅
This is now available in 4.0.0-next.18!
@kripod Ok you've got me, needed to try this out before going to bed ^^
Here's my TypeScript variant, with added types here and there:
In auth.utils.ts:
/** * Authentication configuration */ export interface AuthEnabledComponentConfig { authenticationEnabled: boolean; } /** * A component with authentication configuration */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export type ComponentWithAuth<PropsType = any> = React.FC<PropsType> & AuthEnabledComponentConfig;
In _app.tsx:
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/ban-types type NextComponentWithAuth = NextComponentType<NextPageContext, any, {}> & Partial<AuthEnabledComponentConfig>;
In whatever page:
import React from 'react'; type FooProps = { } const FooPage: ComponentWithAuth<FooProps> = (props: FooProps) => { ... return <foo />; } FooPage.authenticationEnabled = true; export default FooPage;
This seems to be working and feels relatively inoffensive/safe. The _app.tsx file uses
Partial
just to err on the safe side. No hard contract there, but at least it provides some structure.
I'm a bit inexperienced with TS. Where exactly is NextComponentWithAuth
being used? It's in the _app.tsx
file but I don't know what to do with it. Using Component.auth
gives an error that 'auth'
doesn't exist.
function MyApp({ Component, pageProps }: AppProps) {
...
}
I know this is closed, sorry for the necropost, but since the docs reference this issue as an explanation I was reading through anyway. I saw the excellent react-query
implementation and wanted to share a similar one I threw together using the awesome (somewhat similar) swr
library. I'd be open to publishing this at some point if there's any interest. Also, please let me know if you spot a bug, missed edge case, or anything 😄
import useSWR from 'swr';
import { useEffect } from 'react';
import { useRouter } from 'next/router';
const isEmpty = (obj) =>
obj && Object.keys(obj).length === 0 && obj.constructor === Object;
export const useSession = ({
required = true,
redirectTo = '/api/auth/signin?error=SessionExpired',
}) => {
const router = useRouter();
const { data, error, isValidating, ...rest } = useSWR('/api/auth/session');
const session = isEmpty(data) ? null : data;
useEffect(() => {
if (isValidating) return;
if (error || (!session && required)) router.push(redirectTo);
}, [error, isValidating, session, redirectTo, required, router]);
return { session, error, isValidating, ...rest };
};
Note: the isEmpty
stuff is because the default fetcher that swr uses returns truthy {}
and I prefer the !session
ergonomic.
@abdiweyrah I think you can extend your AppProps
like this
export type ProtectedAppProps = AppProps & { Component: NextComponentWithAuth }
@kripod So I think I have found a very neat solution/pattern for this, let me know what you think!
// pages/admin.jsx export default function AdminDashboard () { const [session] = useSession() // session is always non-null inside this page, all the way down the React tree. return "Some super secret dashboard" } AdminDashboard.auth = true
//pages/_app.jsx export default function App({ Component, pageProps }) { return ( <SessionProvider session={pageProps.session}> {Component.auth ? <Auth><Component {...pageProps} /></Auth> : <Component {...pageProps} /> } </SessionProvider> ) } function Auth({ children }) { const [session, loading] = useSession() const isUser = !!session?.user React.useEffect(() => { if (loading) return // Do nothing while loading if (!isUser) signIn() // If not authenticated, force log in }, [isUser, loading]) if (isUser) { return children } // Session is being fetched, or no user. // If no user, useEffect() will redirect. return <div>Loading...</div> }
It can be easily be extended/modified to support something like an options object for role based authentication on pages. An example:
// pages/admin.jsx AdminDashboard.auth = { role: "admin", loading: <AdminLoadingSkeleton/>, unauthorized: "/login-with-different-user" // redirect to this url }
Because of how
_app
is done, it won't unnecessarily contant the/api/auth/session
endpoint for pages that do not require auth.
Do we still need to pass the session in the pageProps like so from every page, since SessionProvider docs says so?
export async function getServerSideProps(ctx) {
return {
props: {
session: await getSession(ctx)
}
}
}
If NOT passing session as page prop then how will the SessionProvider will have session, because it will always be undefined.
it's basically up to you to decide. if you don't pass it there will be a flash of content when you don't have an active session. you can mitigate it by for example adding a nice loading skeleton, like the Vercel dashboard. If you think it's important that your user doesn't see a screen like that and you are fine with increased TTFB, go with getServerSideProps
it's basically up to you to decide. if you don't pass it there will be a flash of content when you don't have an active session. you can mitigate it by for example adding a nice loading skeleton, like the Vercel dashboard. If you think it's important that your user doesn't see a screen like that and you are fine with increased TTFB, go with getServerSideProps
Here I am not worried about the TTFB, but instead, since we are extracting the session
from the pageProps
and if we do not use the getServerSideProps()
to pass session as prop, then no session would be passed to pageProps, and thus the session from pageProps will always be undefined
, so what is the use of then passing the session to SessionProvider - if we are always passing it as undefined
, OR I am missing something here...
if you don't pass the session from somewhere, then we have to fetch it asynchronously, and thus as you say there will be a brief moment with undefined session. not sure what else to say. we cannot make the session appear without actually fetching it first from somewhere. using getServerSideProps shifts this from blocking part of the UI to blocking as a whole. that's the compromise.
@balazsorban44: If you think it's important that your user doesn't see a screen like that and you are fine with increased TTFB, go with getServerSideProps
Perhaps another important consideration here is what data are required for the page and fetched on the server? E.g. An authenticated route might be importing/fetching data and passing along to the page props in getServerSideProps()
. That data should inherently be protected. Without checking the session on the server, we won't have the mechanism to determine if a user should have access to that page's data.
With the existing approach, one can cURL the URL and will have access to that protected data in the server response's __NEXT_DATA__
's pageProps
that are injected onto the page.
One approach we considered to mitigate this was to conditionally pass empty props back from the server:
export async function getServerSideProps(context) {
// Do some data fetching for protected content
return {
props: (await getSession(context)) ? { content } : {},
};
}
Though I expect this might detract from the benefits outlined in this Client Session Handling solution? In which case, do we also lose the ability to manage this pre-session state gracefully?
Edit: In which case might it just make more sense to do a getServerSideProps
redirect
instead?
@abdiweyrah I think you can extend your
AppProps
like this
export type ProtectedAppProps = AppProps & { Component: NextComponentWithAuth }
aand remember renaming Component.auth
to Component.authenticationEnabled
@kripod So I think I have found a very neat solution/pattern for this, let me know what you think!
// pages/admin.jsx export default function AdminDashboard () { const [session] = useSession() // session is always non-null inside this page, all the way down the React tree. return "Some super secret dashboard" } AdminDashboard.auth = true
//pages/_app.jsx export default function App({ Component, pageProps }) { return ( <SessionProvider session={pageProps.session}> {Component.auth ? <Auth><Component {...pageProps} /></Auth> : <Component {...pageProps} /> } </SessionProvider> ) } function Auth({ children }) { const [session, loading] = useSession() const isUser = !!session?.user React.useEffect(() => { if (loading) return // Do nothing while loading if (!isUser) signIn() // If not authenticated, force log in }, [isUser, loading]) if (isUser) { return children } // Session is being fetched, or no user. // If no user, useEffect() will redirect. return <div>Loading...</div> }
It can be easily be extended/modified to support something like an options object for role based authentication on pages. An example:
// pages/admin.jsx AdminDashboard.auth = { role: "admin", loading: <AdminLoadingSkeleton/>, unauthorized: "/login-with-different-user" // redirect to this url }
Because of how
_app
is done, it won't unnecessarily contant the/api/auth/session
endpoint for pages that do not require auth.
@balazsorban44 I am trying to implement the solution above which you have shared in the following : https://simplernerd.com/next-auth-global-session
but in the app.tsx am getting error for component.auth prop as 'Property 'auth' does not exist on type 'NextComponentType<NextPageContext
how to make the auth property available in nextpagecontext properties??
@kripod Ok you've got me, needed to try this out before going to bed ^^
Here's my TypeScript variant, with added types here and there:
In auth.utils.ts:
/** * Authentication configuration */ export interface AuthEnabledComponentConfig { authenticationEnabled: boolean; } /** * A component with authentication configuration */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export type ComponentWithAuth<PropsType = any> = React.FC<PropsType> & AuthEnabledComponentConfig;
In _app.tsx:
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/ban-types type NextComponentWithAuth = NextComponentType<NextPageContext, any, {}> & Partial<AuthEnabledComponentConfig>;
In whatever page:
import React from 'react'; type FooProps = { } const FooPage: ComponentWithAuth<FooProps> = (props: FooProps) => { ... return <foo />; } FooPage.authenticationEnabled = true; export default FooPage;
This seems to be working and feels relatively inoffensive/safe. The _app.tsx file uses
Partial
just to err on the safe side. No hard contract there, but at least it provides some structure.
it works for me ..thank you!!
@balazsorban44 Just to make sure I understand your solution correctly (coming from https://next-auth.js.org/getting-started/client#custom-client-session-handling) : it uses _app.js to wrap .auth
-flagged components with an Auth component that will do the session-fetching (and revalidation) for us.
Thus, it is a higher-level alternative to showing a loading-state in every individual component, right?
How should I interpret the text on https://next-auth.js.org/getting-started/client#custom-client-session-handling that "every page transition afterward will be client-side"? If I refresh a page, the session will still be-refetched, right?
"Due to the way Next.js handles getServerSideProps / getInitialProps, every protected page load has to make a server-side request to check if the session is valid and then generate the requested page. This alternative solution allows for showing a loading state on the initial check and every page transition afterward will be client-side, without having to check with the server and regenerate pages."
Thanks anyways! 👍
@kripod Ok you've got me, needed to try this out before going to bed ^^ Here's my TypeScript variant, with added types here and there: In auth.utils.ts:
/** * Authentication configuration */ export interface AuthEnabledComponentConfig { authenticationEnabled: boolean; } /** * A component with authentication configuration */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export type ComponentWithAuth<PropsType = any> = React.FC<PropsType> & AuthEnabledComponentConfig;
In _app.tsx:
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/ban-types type NextComponentWithAuth = NextComponentType<NextPageContext, any, {}> & Partial<AuthEnabledComponentConfig>;
In whatever page:
import React from 'react'; type FooProps = { } const FooPage: ComponentWithAuth<FooProps> = (props: FooProps) => { ... return <foo />; } FooPage.authenticationEnabled = true; export default FooPage;
This seems to be working and feels relatively inoffensive/safe. The _app.tsx file uses
Partial
just to err on the safe side. No hard contract there, but at least it provides some structure.I'm a bit inexperienced with TS. Where exactly is
NextComponentWithAuth
being used? It's in the_app.tsx
file but I don't know what to do with it. UsingComponent.auth
gives an error that'auth'
doesn't exist.function MyApp({ Component, pageProps }: AppProps) { ... }
@abdiweyrah You can replace this
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/ban-types
type NextComponentWithAuth = NextComponentType<NextPageContext, any, {}> &
Partial<AuthEnabledComponentConfig>;
for this
type AppAuthProps = AppProps & {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Component: NextComponentType<NextPageContext, any, {}> & Partial<AuthEnabledComponentConfig>;
};
function MyApp({ Component, pageProps }: AppAuthProps) {
...
}
This pattern seems to give me an infinite loop of redirection if I have a custom sign-in page, has anyone else experienced this?
pages: { signIn: '/auth/sign-in' }
This pattern seems to give me an infinite loop of redirection if I have a custom sign-in page, has anyone else experienced this?
pages: { signIn: '/auth/sign-in' }
I think i'm in the same boat, here
Getting a typescript error: "Property 'auth' does not exist on type 'FunctionComponent<{}> & { getInitialProps?(context: NextPageContext): {} | Promise<{}>; }'.ts(2339)"
Basically I can't do:
MyComponent.Auth
because MyComponent: NextPage = () => {...}
and NextPage doesn't have auth property... How do I add that again?
Getting a typescript error: "Property 'auth' does not exist on type 'FunctionComponent<{}> & { getInitialProps?(context: NextPageContext): {} | Promise<{}>; }'.ts(2339)"
Basically I can't do:
MyComponent.Auth
becauseMyComponent: NextPage = () => {...}
and NextPage doesn't have auth property... How do I add that again?
@WilderDev Try this.
type NextPageWithAuth<P = {}, IP = P> = NextPage<P, IP> & { auth?: boolean }
const MyComponent: NextPageWithAuth = () => { ... }
Hi, getting the below error in typescript.. can anyone help?
info - Linting and checking validity of types .Failed to compile. ./pages/_app.tsx:12:32 Type error: Cannot find name 'NextPageContext'.
import { NextComponentType } from "next"; import { AuthEnabledComponentConfig } from "../auth.utils";
type AppAuthProps = AppProps<{
session: Session;
}> & {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Component: NextComponentType<NextPageContext, any, {}> & Partial
@newlaravelcoder seems like you need to import NextPageContext
to use it.
Thanks, got it. But still am getting the below error:
Type error: Property 'auth' does not exist on type 'NextComponentType<NextPageContext,
function App({ Component, pageProps }: AppAuthProps) {
return (
console.log(pageProps.session),
); };
Type '{ session: Session; }' has no properties in common with type 'IntrinsicAttributes & { children?: ReactNode; }'.
Edited: Fixed - removing these lines: AppProps<{ session: Session; }> with AppProps
==== FULL CODE (Fixed):
import { Session } from "next-auth";
import { useSession, SessionProvider, signIn } from 'next-auth/react';
import { AppProps } from "next/app";
import "styles/custom.css";
import { NextComponentType, NextPageContext } from "next";
import { AuthEnabledComponentConfig } from "../auth.utils";
type AppAuthProps = AppProps & {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Component: NextComponentType<NextPageContext, any, {}> & Partial<AuthEnabledComponentConfig>;
};
function App({ Component, pageProps }: AppAuthProps) {
return (
console.log(pageProps.session),
<SessionProvider session={pageProps.session}>
{Component.authenticationEnabled ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
)}
</SessionProvider>
);
};
function Auth({ children }) {
// if `{ required: true }` is supplied, `status` can only be "loading" or "authenticated"
const { data: session, status } = useSession({ required: true })
const isUser = !!session?.user
React.useEffect(() => {
if (status === 'loading') return // Do nothing while loading
if (!isUser) signIn()
}, [isUser, status])
if (status === "loading") {
return <div>Loading...</div>
}
if (isUser) {
return children
}
//return children
return <div>Loading...</div>
}
export default App;
I know that has been closed for a while but I think there is always something to contribute. I did some improvements, especially in the TypeScript typings.
Basically, I've created the below type:
export type WithAuthentication<P = unknown> = P & {
requiresAuthentication?: true
}
That allows me to use it on any Next.JS page like this:
const HomePage: WithAuthentication<NextPage> = () => {
...
}
HomePage.requiresAuthentication = true
export default HomePage
And on _app
like this:
/**
* Needed to infer requiresAuthentication as a prop of Component
*/
type ComponentWithAuthentication<P> = P & {
Component: WithAuthentication
}
const MyApp: AppType<{ session: Session | null }> = props => {
const {
Component,
pageProps: { session, ...pageProps },
} = props as ComponentWithAuthentication<typeof props>
const OptionalAuthGuard = Component.requiresAuthentication
? AuthGuard
: Fragment
return (
<SessionProvider session={session}>
<OptionalAuthGuard>
<Component {...pageProps} />
</OptionalAuthGuard>
</SessionProvider>
)
}
Thank you, let me try it out. btw, do you also know how to redirect users based on role using GitHub oath - as soon as redirect, the default user role should be set in the database as 'user' role. Preferably in the middleware (using NextJS 13 & Next-Auth)
I have 4 users and 4 separate dashboard. /adminDashboard - role:admin /internDashboard - role:intern /editorDashboard - role:editor /public - role:user (default)
Thank you again.
authenticationEnabled
getting this error: ReferenceError: AuthGuard is not defined
Also could you explain how this approach is better than the above typescript approach?
Thanks
authenticationEnabled
getting this error: ReferenceError: AuthGuard is not defined
Hei @newlaravelcoder, about your error it seems that you have not imported the AuthGuard
Component. Looking at your code it seems that it is the function Auth({ children }) {
component that you created in _app
. I would say you only need to change the code where I verify if the component has required authentication (Component.requiresAuthentication
). In your case it will be something like:
const OptionalAuthGuard = Component.requiresAuthentication
? AuthGuard
: Fragment
Also could you explain how this approach is better than the above typescript approach?
I like this solution better because I understand that with fewer and reusable types, in the long term, the codebase would be easier to understand. Using typeof props
we are taking the type that was inferred and simply extending it.
Best!
My solution:
const { status, data: session } = useSession({ required: true })
export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
<SessionProvider session={session}>
{Component.auth ? (
<Auth requiredRole={Component.auth.role}> // Step 3
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
)}
</SessionProvider>
)
}
function Auth({ children, requiredRole }) {
const { status, data: session } = useSession({ required: true }) // Step 1
if (status === 'loading') {
return <div>Loading...</div>
}
// Assuming the role is stored in session.user.role
if (session.user.role !== requiredRole) {
return <Redirect to="/auth/login" /> // Step 2
}
return children
}
// Step 2
function Redirect({ to }) {
const router = useRouter()
useEffect(() => {
router.push(to)
}, [to, router])
return null
}
Sounds like a great solution!!
Guys, please help can't understand how to read the token in order to execute api call:
const getAccessToken = async (req: NextApiRequest) => {
const session = (await getSession({ req })) as any;
return session?.accessToken;
};
export const updatePasswordApi = async (data: {
oldPassword: string;
newPassword: string;
}) => {
const accessToken = await getAccessToken() - what and how to pass (req: NextApiRequest);
const response = await fetch(baseUrl, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer {accessToken}`
}
});
if (!response.ok) {
throw new Error(API_ERRORS.UPDATE_PASSWORD);
}
return response.json();
};
i have tried like everything and can't come up with solution
Not sure if I understand your question, but It looks like in the code you provided you are saving the access token as accessToken
and trying to use it as jwt
@emiliosheinz, sorry just updated, my question is how to extract the access token in services that will be used to make api calls, feel like i'm missing some piece but cant see which one exactly
Since this may vary based on things such as where you are calling this function, It's hard to say without the full picture of your project.
Summary of proposed feature
A living session could be a requirement for specific pages. If it doesn’t exist, then the user should be redirected to the sign in page with an error like "Session expired, please try signing in again".
Purpose of proposed feature
Sometimes, a user might log out by accident, or by deleting cookies on purpose. If that happens (e.g. on a separate tab), then
useSession({ required: true })
should detect the absence of a session cookie and always return a non-nullableSession
object type.Detail about proposed feature
If the
required
option is specified, then an effect should be registered to redirect the user to the sign in page as soon as no session is available.Potential problems
The session object could not only become nonexistent, but might even change over time. That edge case should be handled separately.
Describe any alternatives you've considered
Creating a hook in userland, e.g.:
Additional context
As noticed in #1081, NextAuth.js already listens to page visibility changes. Session emptiness checks should be done each time the page becomes visible after hiding it.