In your app’s entrypoint, you’ll see that your application is wrapped in a SessionProvider↗:
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
This context provider allows your application to access the session data from anywhere in your application, without having to pass it down as props:
import { useSession } from "next-auth/react";
const User = () => {
const { data: session } = useSession();
if (!session) {
// Handle unauthenticated state, e.g. render a SignIn component
return <SignIn />;
}
return <p>Welcome {session.user.name}!</p>;
};
Sometimes you might want to request the session on the server. To do so, prefetch the session using the getServerAuthSession
helper function that create-t3-app
provides, and pass it down to the client using getServerSideProps
:
import { getServerAuthSession } from "../server/auth";
import { type GetServerSideProps } from "next";
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getServerAuthSession(ctx);
return {
props: { session },
};
};
const User = () => {
const { data: session } = useSession();
// NOTE: `session` wont have a loading state since it's already prefetched on the server
...
}
user.id
on the SessionCreate T3 App is configured to utilise the session callback↗ in the NextAuth.js config to include the user’s ID within the session
object.
callbacks: {
session({ session, user }) {
if (session.user) {
session.user.id = user.id;
}
return session;
},
},
This is coupled with a type declaration file to make sure the user.id
is typed when accessed on the session
object. Read more about Module Augmentation
↗ on NextAuth.js’s docs.
import { DefaultSession } from "next-auth";
declare module "next-auth" {
interface Session {
user?: {
id: string;
} & DefaultSession["user"];
}
}
The same pattern can be used to add any other data to the session
object, such as a role
field, but should not be misused to store sensitive data on the client.
When using NextAuth.js with tRPC, you can create reusable, protected procedures using middleware↗. This allows you to create procedures that can only be accessed by authenticated users. create-t3-app
sets all of this up for you, allowing you to easily access the session object within authenticated procedures.
This is done in a two step process:
getServerSession
↗ function. The advantage of using getServerSession
instead of the regular getSession
is that it’s a server-side only function and doesn’t trigger unnecessary fetch calls. create-t3-app
creates a helper function that abstracts this peculiar API away so that you don’t need to import both your NextAuth.js options as well as the getServerSession
function every time you need to access the session.export const getServerAuthSession = (ctx: {
req: GetServerSidePropsContext["req"];
res: GetServerSidePropsContext["res"];
}) => {
return getServerSession(ctx.req, ctx.res, authOptions);
};
Using this helper function, we can grab the session and pass it through to the tRPC context:
import { getServerAuthSession } from "../auth";
export const createContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;
const session = await getServerAuthSession({ req, res });
return await createContextInner({
session,
});
};
protectedProcedure
. Any caller to these procedures must be authenticated, or else an error will be thrown which can be appropriately handled by the client.export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
if (!ctx.session || !ctx.session.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
ctx: {
// infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
});
})
The session object is a light, minimal representation of the user and only contains a few fields. When using the protectedProcedures
, you have access to the user’s id which can be used to fetch more data from the database.
const userRouter = router({
me: protectedProcedure.query(async ({ ctx }) => {
const user = await prisma.user.findUnique({
where: {
id: ctx.session.user.id,
},
});
return user;
}),
});
Getting NextAuth.js to work with Prisma requires a lot of initial setup↗. create-t3-app
handles all of this for you, and if you select both Prisma and NextAuth.js, you’ll get a fully working authentication system with all the required models preconfigured. We ship your scaffolded app with a preconfigured Discord OAuth provider, which we chose because it is one of the easiest to get started with - just provide your tokens in the .env
and you’re good to go. However, you can easily add more providers by following the NextAuth.js docs↗. Note that certain providers require extra fields to be added to certain models. We recommend you read the documentation for the provider you would like to use to make sure you have all the required fields.
When adding new fields to any of the User
, Account
, Session
, or VerificationToken
models (most likely you’d only need to modify the User
model), you need to keep in mind that the Prisma adapter↗ automatically creates fields on these models when new users sign up and log in. Therefore, when adding new fields to these models, you must provide default values for them, since the adapter is not aware of these fields.
If for example, you’d like to add a role
to the User
model, you would need to provide a default value to the role
field. This is done by adding a @default
value to the role
field in the User
model:
+ enum Role {
+ USER
+ ADMIN
+ }
model User {
...
+ role Role @default(USER)
}
Usage of NextAuth.js with Next.js middleware requires the use of the JWT session strategy↗ for authentication. This is because the middleware is only able to access the session cookie if it is a JWT. By default, Create T3 App is configured to use the default database strategy, in combination with Prisma as the database adapter.
Using database sessions is the recommended approach and you should read up on JWTs before switching to the JWT session strategy to avoid any security issues.
After switching to the JWT session strategy. Make sure to update the session
callback in src/server/auth.ts
.
The user
object will be undefined
. Instead, retrieve the user’s ID from the token
object.
I.e.:
export const authOptions: NextAuthOptions = {
+ session: {
+ strategy: "jwt",
+ },
callbacks: {
- session: ({ session, user }) => ({
+ session: ({ session, token }) => ({
...session,
user: {
...session.user,
- id: user.id,
+ id: token.sub,
},
}),
},
}
DISCORD_CLIENT_ID
in .env
.DISCORD_CLIENT_SECRET
in .env
. Be careful as you won’t be able to see this secret again, and resetting it will cause the existing one to expire.<app url>/api/auth/callback/discord
(example for local development: http://localhost:3000/api/auth/callback/discord↗
)Resource | Link |
---|---|
NextAuth.js Docs | https://next-auth.js.org/↗ |
NextAuth.js GitHub | https://github.com/nextauthjs/next-auth↗ |
tRPC Kitchen Sink - with NextAuth | https://kitchen-sink.trpc.io/next-auth↗ |