Please refer to the documentation, the example project and existing issues before creating a new issue.
Your question
How is the best way to protect a route?
What are you trying to do
Protect a route.
Documentation feedback
Documentation refers to searching through online documentation, code comments and issue history. The example project refers to next-auth-example.
We need a good example for this!
I'm going to leave this open with examples so that people can refer to it until we do.
If you have a lot of pages that require authentication, you can use these examples into a class and extend pages that require authentication from it or you can write a HOC, to easily add this logic to pages having to add a bunch of boiler plate to each page. We might provide a built-in HOC to help people add pages that require auth more easily in future.
Note: You can also get fancy and put logic in pages/_apps.js but that has issues and I don't recommend it right now - but that might change in future if we can resolve the issues.
This will protect routes both client and server side, but requires JavaScript to be enabled in the client.
import React from 'react'
import { useSession } from 'next-auth/client'
export default () => {
const [ session, loading ] = useSession()
if (loading) {
return <p>Loading…</p>
}
if (!loading && !session) {
return <p>You must be signed in to view this page</p>
}
return <p>Signed in</p>
}
Loading... message in this example.<noscript> tag combined with conditional CSS to handle messaging here if you want to improve what users without JavaScript would see. You could also just return nothing to avoid a flash of content on initial page load.This is useful if you need server side rendering (e.g. to fetch data server side when the user is logged in) with the option to automatically redirect the browser to a sign in page, client side.
import { getSession } from 'next-auth/client'
// This can be a regular component. You can use methods like `componentDidMount()`
// to trigger client side redirect as soon as the component is loaded client side.
const AccessDenied = () =>
<>
<h1>Access Denied</h1>
<p>
<a href="/api/auth/signin">You must be signed in to view this page</a>
</p>
</>
export default ({session}) => {
if (!session) { return <AccessDenied/> }
return (
<Layout>
<h1>Server Side Rendering</h1>
<p>{JSON.stringify(session, null, 2)}</p>
</Layout>
)
}
export async function getServerSideProps(context) {
return {
props: {
session: await getSession(context)
}
}
}
Forces a redirect on both client side and server side rendering.
This is more complicated and don't recommend it right now, but it's here for completeness.
import { getSession } from 'next-auth/client'
const REDIRECT_URL = '/'
const Page = ({session}) => {
if (!session && typeof window !== 'undefined') {
window.location = REDIRECT_URL
return null
}
return (
<>
<h1>Server Side Rendering</h1>
<p>{JSON.stringify(session, null, 2)}</p>
</>
)
}
Page.getInitialProps = async ({req, res}) => {
const session = await getSession({req})
if (!session && req) {
res.writeHead(302, { Location: REDIRECT_URL }).end()
return {}
}
return { session }
}
export default Page
I've had to do something similar on a view projects and it's basically fine but please don't ask me about it. 🙃
There are issues doing redirects from getServerSideProps() which is why it uses getInitialProps()
I wanted this feature inside a API route. But I don't want to send another API call to get the session.
So, I use this:
import jwt from 'next-auth/dist/lib/jwt'
import { JWT_SECRET, SESSION_MAX_AGE } from '../../lib/constants'
export default async function comments(req, res) {
const session = jwt.decode({
secret: JWT_SECRET,
maxAge: SESSION_MAX_AGE,
token: req.cookies['next-auth.session-token']
})
console.log('SESSION', session)
res.status(404).send({})
}
In this case, it uses JWT. But it'll be nice to expose a function to get the session from the core.
The documentation already shows how to do this if you are using a JSON Web Token.
import jwt from 'next-auth/jwt'
const secret = process.env.JWT_SECRET
export default async (req, res) => {
const token = await jwt.getJwt({ req, secret })
res.end(JSON.stringify(token, null, 2))
}
I suggest using the getJWT method as the example above will not work in production with default cookie settings as the cookie name has a secure prefix in production mode.
It is possible to get the session object too, but it's not documented extensively as it will be changing soon.
@iaincollins nice.
I missed that in the doc.
Thanks.
@arunoda
If you wan to get a session from an API route, you can use this:
import { setOptions, getSession } from 'next-auth/client'
setOptions({ site: process.env.SITE })
export default async (req, res) => {
const session = await getSession({ req })
console.log('session', session)
res.end()
}
This is undocumented as may not be relevant in a future release - we will be making it easier, so you don't need to call setOptions() first.
@iaincollins I read that.
Isn't it using a fetch API call to get the session?
https://github.com/iaincollins/next-auth/blob/main/src/client/index.js#L27-L34
I am trying to avoid that call.
I am trying to avoid that call.
Why is that?
Why do you want to make another HTTP call inside another HTTP call?
It's fine it there is no other way. Like calling another service or so.
In this case, you are just calling yourself.
You can get this via a simple method call.
In this case, you are just calling yourself.
Yes, this is absolutely by design.
You can get this via a simple method call.
To call the database directly you would need to pull in all the adapter code and a bunch config of each time and the database adapter would bloat the serverless function size of every API route it is used in - for some database drivers this is tens of megabytes in total.
Including the adapter code and database driver would reduce what it's otherwise possible to do in the API route, as there is a maximum bundle size on serverless functions (and if you are doing anything like built a screen shot service / using something like puppeteer / etc you are already going to be sailing very close to that limit).
The maximum size of a serverless function on Vercel is 50 MB so there isn't a lot of headroom.
Instead, we call the API route to handle fetching the session state, to de-couple the logic.
Fetch is built in to Next.js, so the total impact to the size of an API route by including the client is less than 100 KB. Of course, enabling and using JSON Web Tokens and checking them from API routes is an even cheaper and faster equivalent.
To call the database directly you would need to pull in all the adapter code and a bunch config of each time and the database adapter would bloat the serverless function size of every API route it is used in - for some database drivers this is tens of megabytes in total.
This makes sense when you are using serverless mode. (AKA: Vercel) That's why I am avoiding a DB and use some other mechanism to persist.
In the doc, you have suggested to use API routes. Isn't it serverless?
To me, it seems like next-auth has a model where you deploy the auth server as a typical Node.js server using Heroku or similar.
And use that URL instead of API routes.
Am I missing something here?
If that's the case, what you are suggesting looks fine.
In the doc, you have suggested to use API routes. Isn't it serverless?
To me, it seems like next-auth has a model where you deploy the auth server as a typical Node.js server using Heroku or similar.
The example project is deployed with Serverless. NextAuth.js is built from the ground up for Serverless, you don't need a monolithic server to use (but you can also deploy that way if you want).
Note: Serverless works great with databases too.
AWS even make Serverless specific databases with MySQL and Postgres APIs which work great with NextAuth.js deployed as a Lambda function - they are really inexpensive to run, especially for smaller sites.
NextAuth.js connection handling means you can also use it with any regular database; it will handle re-establishing dropped connections and reconnecting if the Serverless function is suspended (etc).
An example of how to protect a route has now been added to the example project as part of the update to v3:
@iaincollins https://next-auth-example.now.sh/protected returns Access Denied
@rafde Yes, that's because you are not logged in, which is the point of that example.
That's why it says "You must be signed in to view this page".
When you sign in, you can see the page.
@iaincollins Hi, in my case I have a lot of pages that require authentication, which means I have to repeat the following pattern in every page
import React from 'react'
import { useSession } from 'next-auth/client'
export default () => {
const [ session, loading ] = useSession()
if (loading) {
return <p>Loading…</p>
}
if (!loading && !session) {
return <p>You must be signed in to view this page</p>
}
return <p>Signed in</p>
}
If I want to extract this logic, how would I write the HOC? I would appreciate it if you could show me an example.
A basic example HOC
import {getSession} from 'next-auth/client'
export default function withAuth(Component) {
const withAuth = props => {
return <Component {...props} />
}
withAuth.getInitialProps = async ctx => {
return {session: await getSession(ctx)}
}
return withAuth
}
Most helpful comment
We need a good example for this!
I'm going to leave this open with examples so that people can refer to it until we do.
If you have a lot of pages that require authentication, you can use these examples into a class and extend pages that require authentication from it or you can write a HOC, to easily add this logic to pages having to add a bunch of boiler plate to each page. We might provide a built-in HOC to help people add pages that require auth more easily in future.
Note: You can also get fancy and put logic in
pages/_apps.jsbut that has issues and I don't recommend it right now - but that might change in future if we can resolve the issues.Client Side (recommended)
This will protect routes both client and server side, but requires JavaScript to be enabled in the client.
Loading...message in this example.<noscript>tag combined with conditional CSS to handle messaging here if you want to improve what users without JavaScript would see. You could also just return nothing to avoid a flash of content on initial page load.Server Side with Client Side redirect
This is useful if you need server side rendering (e.g. to fetch data server side when the user is logged in) with the option to automatically redirect the browser to a sign in page, client side.
Universal Forced Redirect
Forces a redirect on both client side and server side rendering.
This is more complicated and don't recommend it right now, but it's here for completeness.
I've had to do something similar on a view projects and it's basically fine but please don't ask me about it. 🙃
There are issues doing redirects from
getServerSideProps()which is why it usesgetInitialProps()