Next.js Discord

Discord Forum

Migrating from CRA. Struggling to keep AuthenticatedRoute and UnauthenticatedRoute logic

Answered
Sun bear posted this in #help-forum
Open in Discord
Sun bearOP
Hello,

So, currently I have everything migrated to use pages/ and react-router-dom. It works well, but, I have one page that I'm trying to migrate to the app directory so I can do some SEO + Server Side fetching.

The problem is,

I need to know if the user is authenticated (which is stored in <AuthenticationContext.Provider>)

How do I ensure that if they're authenticated forward them to the client side rendered page/contact.tsx but if not then forward them to app/contact.tsx


Hopefully this makes sense.
Answered by Jboncz
I mean.... do the authentication through middleware, that is shared between pages/app router.
View full answer

301 Replies

Sun bearOP
I guess the main issue is, how, do I have a set of routes that are accessed when authenticated and a set of routes (app/page.tsx) that are accessed when unauthenticated?
Sun bearOP
@Jboncz sorry to tag directly, wondering if you have any idea
Why are you making a new app that uses the pages router?
and why are you using react router?
Im confused.
I have never heard of anyone using react-router inside of nextjs... as its has its own routing schema.
@riský am I crazy thinking this?
Sun bearOP
haha it probably is a bit unnatural. I have a fully built SPA from Create React App. It uses react-router-dom currently
Here is another article with reasons not do that.
Sun bearOP
so I'm using the pages app to just keep everything as a SPA, with rewrites in the next.config.js -- this works well
but I'm trying to slowly migrate to the app directory
Even if we forget about react-router-dom -- How do I in the app directory create an authentication system w/ an external API (I.e I already have an /auth/login on another API that handles auth and sessions)
I mean.... do the authentication through middleware, that is shared between pages/app router.
Answer
but your going about this process wrong, your implementing a half baked solution in pages router, when you have a fully functional SPA app currently. My recommendation would be to migrate directly to app router.
app router is mostly the same if you 'use client' at the top of the page in question.
and if im not mistaken you can still do SEO and Server side fetching in the pages router...
@Jboncz I have never heard of anyone using react-router inside of nextjs... as its has its own routing schema.
Yes, you shouldn't use a other routing thing as nextjs does it
Sun bearOP
@riský @Jboncz just to be clear -- if I was going to get rid of the react-router-dom. I would create all the routes there as directores in app/
and I would have to somehow use next-auth to handle authentication with my external API or whats the best way to handle authentication?
I mean... you dont have to use next auth... Ive never used an auth library tbh. I manage my own auth internally. If you already have a service for authentication, authenticate using route handlers and have your middleware check if you have access on navigation
@Jboncz I mean... you dont have to use next auth... Ive never used an auth library tbh. I manage my own auth internally. If you already have a service for authentication, authenticate using route handlers and have your middleware check if you have access on navigation
disclaimer: My nextjs projects are hosted internally on our work network, I rely on SAML authentication, and handle the POST request that gets sent to my backend when the user attempts to authenticate
Sun bearOP
the route handlers being api/auth/login and just sends a post request to my other API?
Yeah... assuming your other API returns some kind of signed token, then you can shove it in a cookie, and on navigation middleware can reach out to your api and validate it.
Im assuming the 'other api' has all the things it needs to be secure, if thats a bad assumption then.... well idk lol
authentication is a tricky thing, I have the fortune to have some of the complexity taken away from me. You can use next-auth with your current authentication system with a bit of work.
Sun bearOP
yeah i might skip next auth
Sun bearOP
What about all of my RecoilRoot etc
do I just put that in a Layout.tsx but then everything just becomes 'use client'. is that okay?
Thanks for the help btw
Sun bearOP
I want to server side render a lot
but I have Recoil
which if i put that at a high enough level in a Layout.tsx
doesn't it force everything to use client?
Make a client component file which exports that, and import that to layout
Props can be ssr
And client side boundry isn't making children now client
@riský Make a client component file which exports that, and import that to layout
Yes, just because something like a context provider is client side doesnt mean anything inside that context is also client side.
Sun bearOP
right, but, but if everything is rendered in <ClientComponentFile>{children} </ClientComponentFile> and the client component file has 'use client' does that not force all children client compoennt?
ahh
Nope.
Sun bearOP
okay. thank you.
that's good to know
and now a code org question. anything in pages/x.tsx -- where should I put that? lib/?
Otherwise, a simple context provider in a layout would render everything under it client side, which would be pretty crappy
Uhhh what do you mean? Like your actual pages??
I havent used react-router so again, im not familiar.
Sun bearOP
yeah actual pages
The actual pages you will put under app.
/app/pagename/page.tsx
Nextjs is opinionated file based routing
Well unless your using zero libraries everything is opinionated 😛
Sun bearOP
and can I import other Page.tsx
into each other?
Sun bearOP
or should I make that a component
man i really have to re build my whole SPA to do this right..
@Jboncz Well unless your using zero libraries everything is opinionated 😛
I mean react router iirc you get way more contro
Sun bearOP
painful.
You gotta rewind and take a look at npx create-next-app@latest
look at how its tructured.
@Sun bear man i really have to re build my whole SPA to do this right..
Well nextjs isn't SPA, so kinda makes sense
Well yes, your funamentally changing your application.
Sun bearOP
here's my current app
ditto
Sun bearOP
going to turn all of those into folder structure
Yep.
Sun bearOP
and then each "element" into a page / component
and then the UnauthenticatedProvider -- will have to use a middleware
Yeah you could for the time being refact your current 'pages' as components
and then import the component into the page.tsx route
@Sun bear here's my current app
Wow I didn't realise how much simpler nextjs was... React spa sounds pain ... Esp when getting bigger and loading it all
Sun bearOP
should I use dynamic for each of these components? i.e:

const Dashboard = dynamic(() => import('../pages/Dashboard'), { ssr: false })

export default function Page() {
  return 
}
nah.
No reason
Why would you want dynamic rho
Loose server side pretending for Seo and "faster load"
'use client'


at the top of each of your current 'components'
Sun bearOP
yep
yeah main reason I'm doing this
is so i can dynamically generate content for SEO keywords
import { Component } from './actions'

export default function Page(props) {


  return (
    <>
        <Component/>
    </>
  );
}
sorry disregard the misnamings lol, I just fiddled with a page I was helping someone with earlier lol
@Jboncz js import { Component } from './actions' export default function Page(props) { return ( <> <Component/> </> ); }
Can you at least make components uppercase as react conventions
fair 😂
Sun bearOP
and what about my top level page.tsx which should either go to a Login page or my Dashboard.
use redirect in the nextjs config
Sun bearOP
wouldn't this be in middleware.ts?
@Jboncz use redirect in the nextjs config
I use that, would recommend:) (if auth cookie to do dash, otherwise login)
Nah, no need to handle that.
there
@Sun bear wouldn't this be in middleware.ts?
It can, but if it's that simple not nessary
you want middleware as lean as possible, the redirects execute before middleware.
Sun bearOP
can you use auth cookie in the next config?
if theres no logic behind it.
Sun bearOP
am i missing that?
oh i see it
ok
ty
has: [
got it
next question, my friendly friends.
wait wait
backup
Sun bearOP
I owe y'all coffees please send venmo username
where did you see you can use cookies in next config?
Lol
hah
learn something new every day
Sun bearOP
type:'cookie'
Yeah I see that now. cool.
@Jboncz where did you see you can use cookies in next config?
It's what I do... Save me middleware costs on vercel
@riský It's what I do... Save me middleware costs on vercel
Yeah, internal app and all never thought to need it.
I have no cost 😄
Sun bearOP
so next question
@Sun bear I owe y'all coffees please send venmo username
Im good man, just help someone else out when they need it!
Sun bearOP
  15 | export default function Dashboard({ success }: { success?: boolean }) {
> 16 |   const [user]: [Required<User>] = useOutletContext()


I made this Dashboad a use client component, but, it use to have an AuthenticationProvider that would give it the user that is authenticated
how do I pass a user into this component now from middleware or what?
Layout.
Sun bearOP
function AuthenticationProvider({ children }: any) {
  const [isAuthenticated, setIsAuthenticated] =
    useRecoilState(authenticationState)
  const [user, setUser] = useRecoilState(userState)
  const { isLoading } = useUser()
  const reset = () => {
    setUser({} as User)
    setIsAuthenticated(false)
  }

  return (
    <AuthenticationContext.Provider
      value={{
        user,
        isAuthenticated,
        isLoading,
        reset,
      }}
    >
      {!isLoading && <Outlet />}
      {isLoading && <Loading />}
    </AuthenticationContext.Provider>
  )
}
this is my current AuthenticationProvider.. I have to move this into a mixture of API actions?
You will wrap your app in a auth provider in your base layout, or a subset of the base layout whatever alyout you put it in it trickles down.
Sun bearOP
oh I can still use this then?
Yes.
Sun bearOP
ok
also, depending on how you do auth, you could just ssr it and not need to client worry
(and then use suspense for loading state)
True. lemme grab a screenshot real quick so risky can critque more of my code
Sun bearOP
i plan on doing auth where like api/auth/login will just hit a post request to my backend and then smash the JWT into a cookie.
and then it will hit api/profile with that jwt token
every request
nvm, too much company points in there, would take too much effort
Sun bearOP
do we have any examples of how to do super simple auth?
Lol examples I can provide no, sorry.
Sun bearOP
basically this
i have "something" working but idk how safe it is tbh
😂
but i feel like for a large amount, lots of things you added with useEffect or so, could just be server rendered if you have other things already
export function validateAuthenticationtoken(cookie) {
    if (!cookie) {
        cookie = getAuthenticationCookie();
 
        if (!cookie) {
            throw 'Authentication cookie not present';
        }
    }
 
    try {
        const options = {
            issuer: process.env.JwtIssuer,
            expiresIn: (process.env.JwtTimeToLive + 'h'),
            algorithm: process.env.JwtAlgorithm
        };
 
        const jwtToken = jwt.verify(cookie, JWTPublicKey, options)
        return jwtToken;
    }
    catch (error) {
        console.error(`Token Validation Failure: ${error.message}`)
        return false;
    }
};
 
export async function generateAuthenticationToken(access) {
    const options = {
        issuer: process.env.JwtIssuer,
        subject: access.userid,
        expiresIn: (process.env.JwtTimeToLive + 'h'),
        algorithm: process.env.JwtAlgorithm
    };
 
    const jwtPayload = {
        access: access
    }
 
    try {
        const jwtToken = jwt.sign(jwtPayload, jwtPrivateKey, options)
        return jwtToken;
    }
    catch (error) {
        console.log(error)
        return false;
    }
};
This is very very very basic.
@riský but i feel like for a large amount, lots of things you added with useEffect or so, could just be server rendered if you have other things already
and then to be better with db, you can use react cache to dedup and use the "data" between pages and things
@Jboncz js export function validateAuthenticationtoken(cookie) { if (!cookie) { cookie = getAuthenticationCookie(); if (!cookie) { throw 'Authentication cookie not present'; } } try { const options = { issuer: process.env.JwtIssuer, expiresIn: (process.env.JwtTimeToLive + 'h'), algorithm: process.env.JwtAlgorithm }; const jwtToken = jwt.verify(cookie, JWTPublicKey, options) return jwtToken; } catch (error) { console.error(`Token Validation Failure: ${error.message}`) return false; } }; export async function generateAuthenticationToken(access) { const options = { issuer: process.env.JwtIssuer, subject: access.userid, expiresIn: (process.env.JwtTimeToLive + 'h'), algorithm: process.env.JwtAlgorithm }; const jwtPayload = { access: access } try { const jwtToken = jwt.sign(jwtPayload, jwtPrivateKey, options) return jwtToken; } catch (error) { console.log(error) return false; } };
ok i give some too!: https://github.com/RiskyMH/EmailThing/blob/main/app/utils/jwt.ts
import "server-only"
import { cache } from "react"
import { cookies } from "next/headers"
import { jwtVerify, SignJWT } from "jose"
import { env } from "./env"

const JWTToken = new TextEncoder().encode(env.JWT_TOKEN)

export const getUserByToken = cache(
    async (token: string): Promise<string | null> => {
        const jwt = await jwtVerify(token, JWTToken, { algorithms: ["HS256"] })
        const userId = jwt.payload.sub

        if (!userId) return null

        return userId
    },
)

export const createUserToken = (user: { id: string }) => {
    return new SignJWT({ hello: "world" })
        .setProtectedHeader({ alg: "HS256" })
        .setIssuedAt()
        .setNotBefore(Date.now() / 1000 - 60_000)
        .setExpirationTime("7d")
        .setSubject(user.id.toString())
        .sign(JWTToken)
}

export const addUserTokenToCookie = async (user: { id: string }) => {
    const token = await createUserToken(user)
    cookies().set("token", token, {
        expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
        path: "/",
        sameSite: "lax",
        httpOnly: true,
        secure: true,
    })
}

export const getCurrentUser = (async () => {
    const token = cookies().get("token")
    if (!token || !token.value) return null
    try {
        return await getUserByToken(token.value)
    } catch (e) {
        // console.log(e)
        console.log("[JWT_ERROR] Token is invalid or expired. Redirecting to login.")
        return null
    }
})

export const removeToken = () => cookies().delete("token")
and yes i do use react cache here, but idk if its really that much help
I expose two cookies to the client, one signed for auth and one with data the client side needs. The one for client side is just used for visual representations but ALWAYS validated on the backend using the signed one.
When I expose options to users based on access, or navigation authentication
Like the users name for instance, or the team they are on. That stuff is stored in a cookie they COULD manipulate, but it wouldnt actually do anything because I dont rely on it for anything on the backend, I injest the signed on to validate they are valid to do x
hmm that is a good idea, i just check db every time as its not that expensive...
i didnt want to deal with it getting desynced and other issues
I mean, like my navigation bar has their name on it, I dont wanna check the db everytime to display a name.
and my app they cant change their name or anything, because internal app that uses external source for access and datapoints.
I literally hook into I think 3 applications to pull data back. Active Directory, ServiceNow, and Cyberark
Sun bearOP
generateAuthenticationToken and validateAuthenticationtoken are all done on my external API all ready tho
@Jboncz I mean, like my navigation bar has their name on it, I dont wanna check the db everytime to display a name.
me casually using unstable_cache so i dont check db but it gets revalidated on change
Sun bearOP
so really I just need something to POST /external-api/auth/login and then /external-api/profile (Which will have their data, name, etc).

but I don't want to hit external-api/profile every time they switch a page.
@Sun bear generateAuthenticationToken and validateAuthenticationtoken are all done on my external API all ready tho
Right, soooo. Yours is going to need a function to fetch from external api to validate lol
@Jboncz Right, soooo. Yours is going to need a function to fetch from external api to validate lol
Cache the basic info -name, and such in a cookie.
Sun bearOP
but I do need to hit external-api/profile at some point to see if their token is revoked
Middleware.
You have to validate them on every navigation....
I mean I guess you dont have to.... but...
Sun bearOP
but for a SPA rn we only hit /profile the first time
well
yeah
so
Then how do you know when its expired?
Sun bearOP
any time they use the app
it will hit an API
it doesn't have to be profile
that will return a 401
Ahhhhh. Then copy that schema I reckon lol if the users either can or cant access the entire site, then let them access the site and only check when fetching data.
My site, I have roles, views so every navigation attempt I gotta make sure they can access that url which is contained within views
Sun bearOP
yeah we might get there at some point
but a client side fetch api will have to invalidate the server side's middleware?? how's that work in next js?
This is in dev, so its pretty lackluster, I just have the 'super' view which gives me everything lol
Sun bearOP
omg i really have to get this done in a reason able time
this is crazyyyy
You dont need that lol
Are you going to check the users token in middleware everytime they navigate to a 'protected' page?
Thats the first question
Sun bearOP
i don't know what i'm going to do 😂
probably not, because, it will automatically throw them out (ideally if I can figure that out w/ next js) becuase each protected page most likely hit's an API that needs a valid token
Sun bearOP
so those API's (hit from client side) will return a 401
This is essentially as a basic level how I do it
Is this for work? Hosted on internal hardware? Or are you using vercel or some cloud host?
Sun bearOP
vercel
and for work yes
Okay, you have alot more to worry about then... lol because you gotta deal with money things.
Sun bearOP
lmao
if you are server rending pages to give to client, you should check there as if just client... kinda bad
I dont optimize for money, because everything I write is hosted on internal hardware.
@Jboncz Okay, you have alot more to worry about then... lol because you gotta deal with money things.
the if you make money, now pay up (i mean $20/m)
@riský yay
I do very little actualy SSR, because its internal, and speed isnt an issue really.
Sun bearOP
so what should i do
not following
@Jboncz Click to see attachment
Not this.
Sun bearOP
check my external-api/profile (which will validate a token) on every middleware.ts?
Thats an option, that is the 'simplest' option but most cost inefficient.
Sun bearOP
yeah i haven't even gotten to how cost things work w/ vercel
which is problematic
i mean you also loose like most benifits of vercel (namily speed if you have to go that server in edge runtime), so is there an option to host them on same server?
Sun bearOP
no it's a microservice that i don't want to combine them
and this microservice cant just be intregated into nextjs instead im guessing
Sun bearOP
consumers
Oh yeah, then I cant offer you a solution 😂
Sun bearOP
lmao
The guy that made this https://emailthing.xyz/login might tho
that guy is trying to not use vercel for it now lol
😂
Sun bearOP
vercel makes everything expensive huh
they charge by request in middleware?
@Sun bear can we take a step back? Why are you wanting to migrate this 'quickly' like you aluded to?
Sun bearOP
trying to replicate how zoom info (one example)
if you google "Someones name At X company"
the first SEO results is like Linkedin, and then zoominfo
and that's because they create a sitemap with every possible result, and, pre-render that result so google can scrape it
There arent other tools to create a sitemap? There are, that was rhetorical lol, theres nothing that plays nicely with react-router?
Sun bearOP
correct
@Sun bear its a little hard to know whats best for you, as i havent really thought about using vercel but then still slower backend for auth... i just think that vercel isnt the best idea for this and maybe just another service container could work better
like what are you using rn to host app
Sun bearOP
vercel
https://www.pursuit.us/ I like your site tho
oh you are using vercel now
Sun bearOP
haha thanks @Jboncz
good googling @Jboncz
here me out though.... an idea..
hmm the the quickest way is to just use middleware and fetch your api... it may not the the prettist, but it should work
keep your login stuff based in your current react-router...... and only put the front end visible to google stuff in nextjs
so two instances.
tho converting CRA to nextjs app dir i have no idea how fun as havent done myself
Sun bearOP
yeah @Jboncz i have that currently
basically react router is in pages
and i have that owrking
but...
So the current site is running inside of nextjs currently? 😮
Sun bearOP
i got in trouble when I wanted the /contact/:id to go to either pages/Contact.tsx when authenticated or app/unauthenticated/contact/PAge.tsx when unauthenticated
@Jboncz yes
because I have rewrites just always redirecting to /
well if they have the logic for really working it out auth, you could do a simple check of if cookie exists
Sun bearOP
yeah was thinking that but since I have rewrite
      // {
      //   source: '/contact/:name/:contact_id',
      //   destination: '/',
      // },
Man, this thing feels very cobbled together, no offense, just this seems like an 11th hour change.
Sun bearOP
i guess middleware.ts could redirect
@Jboncz startup life baby
@Sun bear <@100490131332431872> startup life baby
For sure, I gotcha.
You got the frontend stuff worked out though, I like the flow of the site.
Sorry I know thats not what we are here for.
Sun bearOP
how would I redirect to pages/Contact.tsx
based on jwt token being available
vs app/Contact.tsx if not
oh
has
in routes
duh
yeah
you already said the answer
Sun bearOP
yeah haha ok
i'm deleting everything i just did
the last hour
and going there
and I learned a thing today too
Sun bearOP
@Jboncz how'd you find the site -- i didn't post anything w/ pursuit did I?
Sun bearOP
guess I gave you all the routes huh
Yep
like if you have expiery cookie for same time as jwt lasts, ther will be basicly no time an actual user will find an issue with it
only time is if you have user who is madness and changed jwt to make invalid or you change jwt method and now other are incorrect
i changed the jwt signing token once, and as i relyed on just it existing, i created infinite loop of /login -> /dash -> /login... it was not fun and i reverted as chaos
Sun bearOP
well you guys have been extremely helpful.
Really appreciate y'all.
One last question.... why did you tag me of all people? xD
lol i like how you then taged me, but it was fun convo so i stayed
😂 Its important to have sanity checking.
and I trust you and knew you wouldnt get mad at me.
Sun bearOP
haha I was doing a deep dive in the discord
and saw you answer something around authentication, and, thought you knew what you were talking about 🙂
I was semi right 😛
Its not always about what you know, its about who you know and how motivated you are to find the answer 😂 Thanks @riský
@Sun bear make sure to mark something in this giant thread as an answer, id give it to risky but the choice is yours.
answer selected by gpt-4 turbo. OP feel free to select a better answer.
hahaha gpt ftw
i think its lazy tho and chose one of the first options
hmm, new feature. noice
Sun bearOP
@riský @Jboncz bringing this back, because, once I deployed to production the reroute method I thought was going to work -- did not work.
that's this rewrite.... the problem is becuase the jwt cookie is an httpOnly cookie sent from my auth microservice.
@Jboncz helped!! sorry for tag
please open a new thread if you have a new question, this one is 298 messages long already
299 300
😉