Quickstart / Fullstack
MojoAuth Hosted Login Page with Next.js
Introduction
Section titled “Introduction”Next.js is a popular React framework that enables server-side rendering, static site generation, and API routes, making it an excellent choice for building modern web applications. Its robust ecosystem and built-in features provide a solid foundation for implementing secure authentication flows.
This guide will walk you through the process of connecting your Next.js application to MojoAuth’s Hosted Login Page using OpenID Connect (OIDC) with next-auth, the most widely adopted and well-maintained authentication library for Next.js.
Links:
Prerequisites
Section titled “Prerequisites”Before you begin, make sure you have:
- A MojoAuth account and an OIDC application set up in the MojoAuth dashboard
- Your Client ID and Client Secret from the MojoAuth dashboard
- Node.js (v16.0.0 or higher) installed on your development machine
- Basic knowledge of Next.js and React
Create a New Next.js Project
Section titled “Create a New Next.js Project”If you don’t have an existing Next.js project, you can create a new one with the following command:
# Create a new Next.js appnpx create-next-app@latest mojoauth-nextjs-examplecd mojoauth-nextjs-exampleIf prompted, you can select the following options:
- Use TypeScript: Yes
- Use ESLint: Yes
- Use Tailwind CSS: Yes (recommended for styling)
- Use
src/directory: Optional - Use App Router: Yes (for modern Next.js features)
Install NextAuth.js
Section titled “Install NextAuth.js”Install NextAuth.js and the necessary dependencies:
npm install next-authConfigure Environment Variables
Section titled “Configure Environment Variables”Create a .env.local file in your project root to store your MojoAuth credentials:
# AuthenticationNEXTAUTH_URL=http://localhost:3000NEXTAUTH_SECRET=your-nextauth-secret-generate-a-secure-random-string
# MojoAuth OIDCMOJOAUTH_CLIENT_ID=your-client-idMOJOAUTH_CLIENT_SECRET=your-client-secretMOJOAUTH_ISSUER=https://your-project.auth.mojoauth.comFor the NEXTAUTH_SECRET, you can generate a secure random string using:
openssl rand -base64 32Configure NextAuth.js
Section titled “Configure NextAuth.js”Create an API Route for Authentication
Section titled “Create an API Route for Authentication”Create a new file at app/api/auth/[...nextauth]/route.ts (for App Router) or pages/api/auth/[...nextauth].ts (for Pages Router):
For App Router (recommended for new projects):
import NextAuth from "next-auth";import { NextAuthOptions } from "next-auth";import OIDCProvider from "next-auth/providers/openid-connect";
export const authOptions: NextAuthOptions = { providers: [ OIDCProvider({ id: "mojoauth", name: "MojoAuth", clientId: process.env.MOJOAUTH_CLIENT_ID as string, clientSecret: process.env.MOJOAUTH_CLIENT_SECRET as string, issuer: process.env.MOJOAUTH_ISSUER as string, wellKnown: `${process.env.MOJOAUTH_ISSUER}/.well-known/openid-configuration`, authorization: { params: { scope: "openid email profile" } }, checks: ["pkce", "state"], client: { token_endpoint_auth_method: "client_secret_post", }, profile(profile) { return { id: profile.sub, name: profile.name, email: profile.email, image: profile.picture, }; }, }), ], callbacks: { async jwt({ token, user, account }) { // Initial sign in if (account && user) { return { ...token, accessToken: account.access_token, accessTokenExpires: Date.now() + (account.expires_in as number) * 1000, refreshToken: account.refresh_token, user, }; }
// Return previous token if the access token has not expired yet if (token.accessTokenExpires && Date.now() < token.accessTokenExpires) { return token; }
// Access token has expired, try to update it return token; }, async session({ session, token }) { session.user = token.user as any; session.accessToken = token.accessToken as string; session.error = token.error as string;
return session; }, }, pages: { signIn: "/auth/signin", error: "/auth/error", }, session: { strategy: "jwt", }, debug: process.env.NODE_ENV === "development",};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };For Pages Router:
import NextAuth, { NextAuthOptions } from "next-auth";import OIDCProvider from "next-auth/providers/openid-connect";
export const authOptions: NextAuthOptions = { providers: [ OIDCProvider({ id: "mojoauth", name: "MojoAuth", clientId: process.env.MOJOAUTH_CLIENT_ID as string, clientSecret: process.env.MOJOAUTH_CLIENT_SECRET as string, issuer: process.env.MOJOAUTH_ISSUER as string, wellKnown: `${process.env.MOJOAUTH_ISSUER}/.well-known/openid-configuration`, authorization: { params: { scope: "openid email profile" } }, checks: ["pkce", "state"], client: { token_endpoint_auth_method: "client_secret_post", }, profile(profile) { return { id: profile.sub, name: profile.name, email: profile.email, image: profile.picture, }; }, }), ], callbacks: { async jwt({ token, user, account }) { // Initial sign in if (account && user) { return { ...token, accessToken: account.access_token, accessTokenExpires: Date.now() + (account.expires_in as number) * 1000, refreshToken: account.refresh_token, user, }; }
// Return previous token if the access token has not expired yet if (token.accessTokenExpires && Date.now() < token.accessTokenExpires) { return token; }
// Access token has expired, try to update it return token; }, async session({ session, token }) { session.user = token.user as any; session.accessToken = token.accessToken as string; session.error = token.error as string;
return session; }, }, pages: { signIn: "/auth/signin", error: "/auth/error", }, session: { strategy: "jwt", }, debug: process.env.NODE_ENV === "development",};
export default NextAuth(authOptions);Update TypeScript Types (Optional)
Section titled “Update TypeScript Types (Optional)”If you’re using TypeScript, create a types file to extend the NextAuth types with next-auth.d.ts in your project root:
import NextAuth from "next-auth";
declare module "next-auth" { interface Session { accessToken?: string; error?: string; user: { id: string; name?: string | null; email?: string | null; image?: string | null; } & DefaultSession["user"]; }
interface JWT { accessToken?: string; accessTokenExpires?: number; refreshToken?: string; error?: string; user?: { id: string; name?: string | null; email?: string | null; image?: string | null; }; }}Create Auth Provider Component
Section titled “Create Auth Provider Component”For App Router
Section titled “For App Router”Create a file at app/context/AuthProvider.tsx:
'use client';
import { SessionProvider } from "next-auth/react";import { ReactNode } from "react";
export default function AuthProvider({ children }: { children: ReactNode }) { return <SessionProvider>{children}</SessionProvider>;}Then update your root layout at app/layout.tsx: