Replies: 18 comments 19 replies
-
FWIW, I have worked around this now by using a custom sign in page in the middleware config, with the page immediately calling signin for my provider from the browser on load. This works, but feels a bit clunky as it's another few calls and redirects before the user ends up on the actual login page. |
Beta Was this translation helpful? Give feedback.
-
i need the option like skip choose provider too |
Beta Was this translation helpful? Give feedback.
-
Any updates on this? There is a way to do this client-side by passing provider in |
Beta Was this translation helpful? Give feedback.
-
Enabling this functionality in middleware would make the login process much cleaner as an application that uses only one provider. Would love to see this implemented |
Beta Was this translation helpful? Give feedback.
-
I'd love to see this. +1 |
Beta Was this translation helpful? Give feedback.
-
yeah, i think if we have a possibility to add a providers in the middleware would be clear to manage authorizations/authentications 🤞 |
Beta Was this translation helpful? Give feedback.
-
I use keycloak so any extra providers in addition to the standard email/password login, such as Google etc, are setup directly in keycloak. Because of this I also had the need to bypass this list screen and just skip straight to keycloak to keep the auth process slick. This is how I've done it: Created 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 type {
NextAuthMiddlewareOptions,
NextMiddlewareWithAuth, WithAuthArgs,
} from "next-auth/middleware";
import {BuiltInProviderType, RedirectableProviderType} from "next-auth/providers";
import { getCsrfToken } from "next-auth/react";
import {LiteralUnion} from "next-auth/react/types";
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 & {provider?: string} | 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;
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/${options.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,
// Modified from the original middleware as redirect did not seem to work for me with req.url
callbackUrl: req.nextUrl.pathname,
json: "true",
}),
});
const data = (await res.json()) as { url: string };
return NextResponse.redirect(data.url, {
headers: {
"Set-Cookie": res.headers.get("set-cookie") ?? ""
},
});
}
export declare type WithAuthProviderArgs = [
...WithAuthArgs & [
{
provider: LiteralUnion<RedirectableProviderType | BuiltInProviderType>
}
]
];
export function withAuth(...args: WithAuthProviderArgs) {
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 function withAuthProvider(provider: LiteralUnion<RedirectableProviderType | BuiltInProviderType>, ...args: WithAuthArgs) {
return withAuth({...args, provider});
} Now, in your next import { withAuthProvider } from "./middlewares/withAuthorization";
export default withAuthProvider('keycloak');
// You could also use the standard withAuth, and specify the provider. e.g. withAuth({ provider: 'keycloak' })
export const config = {
matcher: [
// Example of securing anything under /secured
"/secured",
"/secured/(.*)",
],
}; Seems to work okay, but I'm not exactly a ts pro, so if anyone wants to test or has improvements that would be great. BTW, if you specify a provider that you don't have setup it should default back to the provider list screen. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
One good/bad side effect to auto-selecting the only provider is that if you log out and then hit a protected route, it will immediately re-login. You can't stop the page from logging in any more 🙃, except temporary by specifying a non-protected URL to signOut, like -Until someone comes up with some clever flag. If manually selecting a login provider, the user must click at least 1 button to actually login. Even if it's the only button. But I agree it looks like a UX bug to show just 1 option to a user 😁 In that case it would be better to have a secondary "abort" button so that users at least have a choice to do nothing… |
Beta Was this translation helpful? Give feedback.
-
Can we revive this idea, please? |
Beta Was this translation helpful? Give feedback.
-
I have a client solution, my providers will change in different sites, some site has only one provider, so I can use Refer: eg: import { getProviders, signIn, signOut } from "next-auth/react";
const handleSignIn = async () => {
console.log("Sign-in start");
try {
const providers = await getProviders();
let autoSign: BuiltInProviderType | undefined = undefined;
if (providers && Object.keys(providers).length === 1) {
let current = providers[Object.keys(providers)[0]];
if (current.type === 'oauth') {
autoSign = current.id as BuiltInProviderType
}
}
await signIn(autoSign, {
callbackUrl: generateLoginCallbackUrl(),
redirect: true,
});
} catch (error) {
console.error("Error during sign-in:", error);
} finally {
console.log("Sign-in end");
}
}; |
Beta Was this translation helpful? Give feedback.
-
Any updates on this? |
Beta Was this translation helpful? Give feedback.
-
Also looking for a feature like this. There isn't much sense in displaying a page with a single button when there's only one provider. |
Beta Was this translation helpful? Give feedback.
-
Totally agree! |
Beta Was this translation helpful? Give feedback.
-
This seriously needs to be implemented, I only have 1 auth provider. |
Beta Was this translation helpful? Give feedback.
-
This worked for me. #4078 (comment) |
Beta Was this translation helpful? Give feedback.
-
Description 📓
I use nextauth together with Auth0 as the only provider, and basically always want to use the Auth0 universal login page as the login page, instead of a local one in Next, the default one provided by NextAuth, etc.
For login triggered by the client, this can be done by always specifically selecting the provider, e.g. signIn('auth0'), but I see no matching option for this when using middleware.
It would be highly valuable to be able to do this through the middleware for protecting some pages, to centralise this login in one place instead of having to rely on client side checks for these pages.
How to reproduce ☕️
Contributing 🙌🏽
No, I am afraid I cannot help regarding this
Beta Was this translation helpful? Give feedback.
All reactions