How to auto redirect to the provider login screen #4078
-
I have a single provider (Okta) to login users to the portal. The portal does not have a landing page or any unauthenticated pages. Hence, I would like to have the following behavior:
I cannot find the option to bypass the NextAuth login page? Any idea how to resolve this is welcomed! |
Beta Was this translation helpful? Give feedback.
Replies: 14 comments 74 replies
-
See: here |
Beta Was this translation helpful? Give feedback.
-
Thanks @balazsorban44 . The solution I came across.
import { signIn, useSession } from "next-auth/react";
import { useEffect } from "react";
import { useRouter } from "next/router";
export default function Signin() {
const router = useRouter();
const { status } = useSession();
useEffect(() => {
if (status === "unauthenticated") {
console.log("No JWT");
console.log(status);
void signIn("okta");
} else if (status === "authenticated") {
void router.push("/");
}
}, [status]);
return <div></div>;
}
import { withAuth } from "next-auth/middleware";
export default withAuth({
pages: {
signIn: "/auth/signin",
},
});
|
Beta Was this translation helpful? Give feedback.
-
And just in case anyone is interested in redirecting to the callback url instead of the root: void router.push(router.query.callbackUrl); |
Beta Was this translation helpful? Give feedback.
-
Tried this in Next 12.2 with no luck. I think due to middleware running on _next requests too now, catch-alls like this break. I was able to get it working with the following middleware. Its pretty jank, essentially I had to override the default callback so it would always defer parsing to the middleware function. From there you can conditionally route based on pathname. import { JWT } from "next-auth/jwt";
import { withAuth } from "next-auth/middleware"
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
export default withAuth(
(req: NextRequest & { nextauth: { token: JWT | null } }) => {
if (req.nextUrl.pathname.startsWith('/auth') || req.nextUrl.pathname.startsWith('/_next')) {
return NextResponse.next();
}
if (!req.nextauth.token) {
const loginUrl = new URL("/auth/signin", req.url)
loginUrl.searchParams.set('from', req.nextUrl.pathname)
return NextResponse.redirect(loginUrl)
}
return NextResponse.next();
},
{
callbacks: {
authorized() {
return true
},
},
}
); Feels like there should be a better way of doing this? |
Beta Was this translation helpful? Give feedback.
-
@meleksomai The above solution is not working for me. The redirect back to '/' never happens and the app seems stuck on at Here is my
Here is my
There seems to be an infinite loop happening for some reason |
Beta Was this translation helpful? Give feedback.
-
For keycloak, any other way to redirect to keycloak login page instead redirecting from a custom signin page? |
Beta Was this translation helpful? Give feedback.
-
My team is using Next.js 13 app directory. We found that the signin page solution was not going to do the job. Our solution simply takes next-auth's solution and combines it with some of the sign in implementation. I'm not super happy with this yet as I would prefer not to make api fetches to the server but this does work and it's relatively clean. If you are using an SSO provider then you will need to make sure any sign out button actually signs you out from the provider and not just the app or you will just be signed back in. I can provide further guidance on our solution for that as well. // middleware.ts
/**
* See [Edge Runtime](https://nextjs.org/docs/api-reference/edge-runtime)
* for more information about Next.js middleware.
*/
import type { NextMiddleware, NextRequest } from "next/server";
import { NextResponse } from "next/server";
import type { JWT } from "next-auth/jwt";
import { getToken } from "next-auth/jwt";
import { getCsrfToken } from "next-auth/react";
import type {
NextAuthMiddlewareOptions,
NextMiddlewareWithAuth,
WithAuthArgs,
} from "next-auth/middleware";
import { DEFAULT_PROVIDER } from "./utils/auth";
/**
* This hash function relies on Edge Runtime.
* Importing node.js crypto module will throw an error.
*/
async function hash(value: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(value);
const hash = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hash));
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
}
interface AuthMiddlewareOptions extends NextAuthMiddlewareOptions {
trustHost?: boolean;
}
async function handleMiddleware(
req: NextRequest,
options: AuthMiddlewareOptions | undefined = {},
onSuccess?: (token: JWT | null) => ReturnType<NextMiddleware>
) {
const { origin, basePath } = req.nextUrl;
const errorPage = options?.pages?.error ?? "/api/auth/error";
options.trustHost ??= !!(
process.env.NEXTAUTH_URL ??
process.env.VERCEL ??
process.env.AUTH_TRUST_HOST
);
const host =
process.env.NEXTAUTH_URL ??
req.headers?.get("x-forwarded-host") ??
"http://localhost:3000";
options.secret ??= process.env.NEXTAUTH_SECRET;
if (!options.secret) {
console.error(
`[next-auth][error][NO_SECRET]`,
`\nhttps://next-auth.js.org/errors#no_secret`
);
const errorUrl = new URL(`${basePath}${errorPage}`, origin);
errorUrl.searchParams.append("error", "Configuration");
return NextResponse.redirect(errorUrl);
}
const token = await getToken({
req,
decode: options.jwt?.decode,
cookieName: options?.cookies?.sessionToken?.name,
secret: options.secret,
});
const isAuthorized =
(await options?.callbacks?.authorized?.({ req, token })) ?? !!token;
// the user is authorized, let the middleware handle the rest
if (isAuthorized) return onSuccess?.(token);
const cookieCsrfToken = req.cookies.get("next-auth.csrf-token")?.value;
const csrfToken =
cookieCsrfToken?.split("|")?.[0] ?? (await getCsrfToken()) ?? "";
const csrfTokenHash =
cookieCsrfToken?.split("|")?.[1] ??
(await hash(`${csrfToken}${options.secret}`));
const cookie = `${csrfToken}|${csrfTokenHash}`;
const res = await fetch(`${host}/api/auth/signin/${DEFAULT_PROVIDER}`, {
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-Auth-Return-Redirect": "1",
cookie: `next-auth.csrf-token=${cookie}`,
},
credentials: "include",
redirect: "follow",
body: new URLSearchParams({
csrfToken,
callbackUrl: req.url,
json: "true",
}),
});
const data = (await res.json()) as { url: string };
return NextResponse.redirect(data.url, {
headers: {
"Set-Cookie": res.headers.get("set-cookie") ?? "",
},
});
}
export function withAuth(...args: WithAuthArgs) {
if (!args.length || args[0] instanceof Request) {
return handleMiddleware(...(args as Parameters<typeof handleMiddleware>));
}
if (typeof args[0] === "function") {
const middleware = args[0];
const options = args[1] as NextAuthMiddlewareOptions | undefined;
return async (...args: Parameters<NextMiddlewareWithAuth>) =>
await handleMiddleware(args[0], options, async (token) => {
args[0].nextauth = { token };
return await middleware(...args);
});
}
const options = args[0];
return async (...args: Parameters<NextMiddleware>) =>
await handleMiddleware(args[0], options);
}
export default withAuth({});
export const config = {
matcher: [
// This matcher is necessary to capture the origin route
"/",
// Do not match public pages or auth routes
"/((?!api/auth|_next|public|favicon.ico|logo.svg).*)",
],
}; |
Beta Was this translation helpful? Give feedback.
-
It'd be nice if something like this worked. Just have the signin page receive a provider name (the callback endpoint already does this) and skip the login page, but doing this shows the login page: export const getServerSideProps: GetServerSideProps<{}> = async (context) => {
//TODO: I'd prefer to do this server side, but for some reason this is not going to okta directly
const session = await getServerSession(context.req, context.res, authOptions);
if (!session) {
return {
redirect: {
destination: "/api/auth/signin/okta",
statusCode: 302,
},
};
}
return {
props: {},
};
}; |
Beta Was this translation helpful? Give feedback.
-
I adapted @meleksomai's code for the Next.js 13 app router.
'use client';
import { SessionProvider, getSession } from "next-auth/react";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await getSession();
return (
<SessionProvider session={session}>
<html lang="en">
<body>
{children}
</body>
</html>
</SessionProvider>
);
}
'use client';
import { signIn, useSession } from 'next-auth/react';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
export default function Signin() {
const router = useRouter();
const { status } = useSession();
useEffect(() => {
if (status === 'unauthenticated') {
console.log('No JWT');
console.log(status);
void signIn('okta');
} else if (status === 'authenticated') {
void router.push('/');
}
}, [status]);
return <div></div>;
} In my own case, I gave the page a little bit of styling, a spinner, and a "Logging in..." message. |
Beta Was this translation helpful? Give feedback.
-
In order to achieve the same redirect behavior as the original NextAuth sign in page you can update the component like so:
|
Beta Was this translation helpful? Give feedback.
-
"use client"; if (status === "authenticated" && session?.user?.jwt && !session) { export default Navbar; |
Beta Was this translation helpful? Give feedback.
-
Been tinkering with this issue on my side as well. A few slight changes to @jmiller-rise8 's recipe. By the way, thanks immensely for putting it up! It's been very helpful. As with @renet , the the
However, still need to specify the correct
|
Beta Was this translation helpful? Give feedback.
-
In case anyone is looking for some alternative approach, this is what I ended up doing: //auth.ts
NextAuth({
pages: {
signIn: "/api/signin",
},
//... rest of your config
}) // api/signin/route.ts
import { signIn } from "@/auth";
export async function GET(req: Request) {
const searchParams = new URL(req.url).searchParams;
return signIn("auth0", { redirectTo: searchParams.get("callbackUrl") ?? "" });
} that way I didn't have to deal with pages or useEffects |
Beta Was this translation helpful? Give feedback.
-
This also should work just fine // auth.ts
export const routes = {
signInPage: '/auth/signin',
signInErrorPage: '/auth/error',
signOutPage: '/auth/signout',
} as const;
export const { handlers, signIn, signOut, auth } = NextAuth({
providers,
pages: {
error: routes.signInErrorPage,
signIn: routes.signInPage,
signOut: routes.signOutPage,
},
callbacks: {
async authorized({ request, auth }) {
const isSignedIn = !!auth?.user;
const url = request.nextUrl;
// Redirect all non-public routes to the sign-in page
if (!isSignedIn && !url.pathname.startsWith(routes.signInPage)) {
return Response.redirect(new URL(routes.signInPage, url));
}
return isSignedIn;
},
async jwt({ token, account, user }) {
... |
Beta Was this translation helpful? Give feedback.
Thanks @balazsorban44 . The solution I came across.
pages
, for instance/pages/auth/signin.tsx
:_middleware.tsx
to redirect to the custom sign-in page.import