-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to create a custom sign-in page in Auth.js? #9189
Comments
hi. can you show me the code example of all configutation include handle error message using credential provider please |
Note I'm posting directly to The code for the error message looks like this (in const error = context.query["error"];
// the error messages are copied from somewhere in the NextAuth source code, don't remember where
const errors: Record<SignInErrorTypes, string> = {
Signin: "Try signing in with a different account.",
OAuthSignin: "Try signing in with a different account.",
OAuthCallback: "Try signing in with a different account.",
OAuthCreateAccount: "Try signing in with a different account.",
EmailCreateAccount: "Try signing in with a different account.",
Callback: "Try signing in with a different account.",
OAuthAccountNotLinked:
"To confirm your identity, sign in with the same account you used originally.",
EmailSignin: "The e-mail could not be sent.",
CredentialsSignin:
"Sign in failed. Check the details you provided are correct.",
SessionRequired: "Please sign in to access this page.",
default: "Unable to sign in.",
};
const errorMessage =
error && typeof error === "string" && errors[error as SignInErrorTypes]; |
did you have some experience on set up credentials login with custom page using beta version of authjs ? i set up it but it hard to catch error message when throw from authorize function. it throw error message in console when i throw error from it. |
I agree we need the docs here updated: https://authjs.dev/guides/basics/pages the serversideprops on inside app is not longer possible. |
So is it possible to have a custom signin page given getProviders is not exported in next-auth@5.0.0-beta.3? Does anyone know of the new sample code to use? |
I was asking myself the same question. Docs are in really bad shape atm, especially since Next14 and v5 changed the way you can handle authentication. |
Would really like a quick documentation or pointers of building custom auth pages. I am building a new website using authjs and I am stuck at this step. |
Hi, I'm using v5 in combination with Next 14. My custom login page is using the signin function created by NextAuth(). It's really straightforward when you are using server actions since the csrf stuff is handled for you internally. To get error and loading states working just use useFormState and useFormStatus. |
Cool, thanks for letting me know about this. Will definitely try it soon, must port to app router first. How would you get the list of enabled providers? Is this still possible? Am I missing something? If there are documentation available that would be really helpful as well |
I'm a new nextjs and authjs user. Would it be possible to share a small snippet or to update the docs on how to get the custom Auth page working? |
I've managed to get it working with the app router. I don't know if it's possible with the pages router. Here are some snippets for OAuth providers and the Email provider: // SignInOAuthComponent.tsx
"use client";
import { useState, useTransition } from "react";
import { signInOAuth } from "actions/signin";
export default function SignInOAuthComponent() {
const [error, setError] = useState(null);
const [, startTransition] = useTransition();
return <>
{error && <div>{error}</div>}
<button type="button" onClick={() => {
startTransition(async () => {
const result = await signInOAuth({
providerId: 'google',
});
if (result?.status === "error") {
setError(result.errorMessage);
}
});
}}>
Sign in with Google
</button>
</>
} // SignInEmailComponent.tsx
"use client";
import { useFormState } from "react-dom";
import { signInEmail } from "actions/signin";
export default function SignInEmailComponent() {
const [state, formAction] = useFormState(signInEmail, null);
return <form action={formAction}>
<label>
E-mail address:
<input type="email" name="email" />
</label>
<button type="submit">Sign in with email</button>
{state?.status === "error" && (
<p>{state.errorMessage}</p>
)}
</form>
} // actions/signin.ts
import { redirect } from "next/navigation";
import {
type RedirectableProviderType,
type OAuthProviderType,
} from "next-auth/providers";
/**
* Server action for OAuth sign in with AuthJS.
*/
export async function signInOAuth({ providerId }: { providerId: string }) {
let redirectUrl: string | null = null;
try {
// note: could validate the providerId using something like zod to ensure only allowed providers are passed in
// The signIn() function comes from NextAuth() in your auth.ts
redirectUrl = await signIn(providerId satisfies OAuthProviderType, {
redirect: false,
});
if (!redirectUrl) {
return {
status: "error",
errorMessage: "Failed to login, redirect url not found",
} as const;
}
} catch (error) {
return {
status: "error",
errorMessage: "Failed to login",
} as const;
}
redirect(redirectUrl);
}
export type SignInEmailResult =
| {
status: "error";
errorMessage: string;
}
| undefined;
/**
* Server action for email sign in with AuthJS.
*/
export async function signInEmail(
previousState: SignInEmailResult | null, // please the compiler..
formData: FormData,
): Promise<SignInEmailResult> {
let redirectUrl: string | null = null;
try {
const email = formData.get("email");
// The signIn() function comes from NextAuth() in your auth.ts
redirectUrl = await signIn("email" satisfies RedirectableProviderType, {
redirect: false,
email,
});
if (!redirectUrl) {
return {
status: "error",
errorMessage: "Failed to sign in using email, redirect url not found",
} as const;
}
} catch (error) {
return {
status: "error",
errorMessage: "Failed to sign in using email.",
} as const;
}
redirect(redirectUrl);
} |
@wunderlichh, if you have a moment, can you link to some details about this? It looks to me like it still needs to be provided to the login form. The docs here suggest one should be able to get a token through the API and then it looks like it needs to be in a form variable @kjetilhartveit, maybe you know something about this too? Thank you! |
The docs you linked to also state certain frameworks have built-in CSRF protection so perhaps that's the case for NextJS. I'm not too knowledgable on this though so would also love some pointers around this. Their official examples for the Here's the example from the official jsdocs for the import { AuthError } from "next-auth"
import { signIn } from "../auth"
export default function Layout() {
return (
<form action={async (formData) => {
"use server"
try {
await signIn("credentials", formData)
} catch(error) {
if (error instanceof AuthError) // Handle auth errors
throw error // Rethrow all other errors
}
}}>
<button>Sign in</button>
</form>
)
} |
@kjetilhartveit this is helpful and I think it led me to something important. That snippet from the jsdoc is an example of the
That value comes in as a prop. This combined with references to double-submit CSRF protection tell me that this only works if you provide that field, which expects you to get the CSRF token ahead of time. You can get it by doing a |
I spent some time on this today. I'm using Next.js 14.0.4 with
You can do it manually like this:
An extremely crude minimal example that uses RSC, Server Actions, and the new // page.tsx, RSC
// You can do any pre-auth stuff here. Make sure they're not logged in and redirect if they are.
import { loginAction } from '@/controllers/admin/authController';
const LoginPage = async () => {
return <LoginForm onSubmitForm={loginAction} />;
};
export default LoginPage; 'use client';
// LoginForm.tsx
// This makes the CSRF request and presents the form.
import * as React from 'react';
import { use } from 'react';
type SubmitFormData = {
email: string;
password: string;
csrfToken: string;
};
const LoginForm = ({
onSubmitForm,
}: {
onSubmitForm: (formData: SubmitFormData) => Promise<{ status: string } | undefined>;
}) => {
const memoizedRequest = React.useMemo(() => {
return new Promise<string>((res) => {
void fetch(`${YOUR_DOMAIN}/api/auth/csrf`, {
next: {
revalidate: 0,
},
}).then((csrfTokenResponse) => {
return csrfTokenResponse.json().then((json) => {
const { csrfToken } = json as { csrfToken: string };
res(csrfToken);
});
});
});
}, []);
// FYI, on closer reflection, I think my use of `use` is wrong here. You can assign the csrfToken in a `useMemo` instead. Show a loading state while fetching?
const csrfToken = use(memoizedRequest);
return (
<div>
<form
onSubmit={(formData) => {
const formDataAsObject = Object.fromEntries(new FormData(formData.currentTarget));
void onSubmitForm(formDataAsObject as SubmitFormData);
}}
>
<input type="hidden" name="csrfToken" value={csrfToken} />
<label>
Username
<input type="text" name="email" id="email" />
</label>
<label>
Password
<input type="password" name="password" id="password" />
</label>
<button type="submit">Submit</button>
</form>
</div>
);
};
export default LoginForm; 'use server';
// The React Server Action that is responsible for login
import { isRedirectError } from 'next/dist/client/components/redirect';
import { cookies } from 'next/headers';
import { z } from 'zod';
import { signIn } from '@/lib/auth';
const loginSchema = z.object({
email: z.string().email(),
password: z.string(),
csrfToken: z.string(),
});
export async function loginAction(unparsedFormData: { email: string; password: string; csrfToken: string }) {
const formData = loginSchema.parse(unparsedFormData);
const requestCookies = cookies();
const csrfToken = requestCookies.get('authjs.csrf-token');
const tokenValue = csrfToken?.value?.split('|').at(0);
if (formData.csrfToken !== tokenValue) {
throw new Error('CSRF token mismatch');
}
try {
// This will redirect immediately on login success
await signIn('credentials', unparsedFormData);
} catch (e) {
if (isRedirectError(e)) {
throw e;
}
console.log('error', e);
return {
status: 'error',
};
}
} I'm not sure if I'm using It's possible that there's more to parsing the token than this. Please correct any mistakes or misunderstandings. Thanks for your time and help, all. 🙏 As an alternative to some of this, you should just be able to read the cookie client-side. NextAuth with Next.js sets the cookie for you without making a request so you can just grab it, stick it in the form, and parse it on the server. |
@mwawrusch commented to the big general v5 Discussion that you can add This is working for me. I replaced my
I also found that in production, the cookie name becomes
|
This should be changed to: redirectUrl = await signIn("nodemailer", {
redirect: false,
email
}) Reason being they changed the id of the email provider found here. |
Hey folks, we recently shipped a new docs site which includes detailed pages on setting up custom pages in auth.js. Including a custom signin page. If you have any additional issues still, feel free to open a new issue 🙏 |
Hi ndom91, I didn't want to open a new issue to avoid losing the context of the conversation. By reading the documentation shown in your suggested post about custom signin page, it would seem as an easy taks to do. However, I am getting the error "[auth][error] MissingCSRF: CSRF token was missing during an action signin. Read more at https://errors.authjs.dev#missingcsrf"." The provided auth.js documentation guides me to this page "https://developer.mozilla.org/en-US/docs/Web/Security/CSRF" which throws a 404 error. Since it seems something easy to do, I am wondering if this is something I should research and solve outside the library "auth.js" or if it something not working from auth.js end. I appreciate any comment on this, thanks. |
I have the same problem as @lob0codes. Here is my custom login page: import loginAction from './loginAction';
export default async function Login() {
return (
<Center mih="100dvh">
<form action={loginAction}>
<TextInput type="email" name="email" id="email" placeholder="enter your email address" />
<Button type="submit">Sign in</Button>
</form>
</Center>
);
} And my action: export default async function loginAction(formData: FormData) {
console.log('the formData', formData);
try {
await signIn('nodemailer', {
email: formData.get('email'),
redirectTo: '/',
});
} catch (error) {
return {
status: 'error',
errorMessage: 'Failed to sign in',
};
}
return {
status: 'success',
};
} The first time I sign in, everything works. However, if I log out and then try to sign in again, I get
|
@subvertallchris, sorry to ping you like this, I was wondering if you might have some insight into what's going on with my CSRF token... I've followed your example above, using the RSC -> client rendered form -> server action pattern. Here's my form:
and my action: 'use server';
import { cookies } from 'next/headers';
import { signIn } from '@/lib/auth';
type Status = 'success' | 'error' | 'not_sent';
export type ActionState = {
status: Status;
errorMessage?: string;
};
export default async function loginAction(prevState: ActionState, formData: FormData) {
console.log('CALLED');
const requestCookies = cookies();
const csrfToken = requestCookies.get('authjs.csrf-token');
const tokenValue = csrfToken?.value?.split('|').at(0);
if (formData.get('csrfToken') !== tokenValue) {
throw new Error('CSRF token mismatch');
}
console.log('the submitted form data', Object.fromEntries(formData));
try {
await signIn('nodemailer', {
...Object.fromEntries(formData),
redirectTo: '/',
});
} catch (error) {
return {
status: 'error' as Status,
errorMessage: 'Failed to sign in',
};
}
return {
status: 'success' as Status,
};
} What's interesting is that when I login the first time, this works. However, if I then sign out, and then try to login again, I get the Just asking here because you clearly know your stuff, so any help would be hugely appreciated! |
Hi raph90, Check this link, it may help you solve the issue, I think is an issue with Next.js: ndom91/next-auth-example-sign-in-page#5 I haven't had time to keep testing the solution but I tried a few times and the solution seems to work. |
@lob0codes Hi!!! Did you get this to work properly? I am struggling with this, seems to be too complicated it and it is supposed to be easy :( |
What is the improvement or update you wish to see?
The examples on https://authjs.dev/guides/basics/pages depicting custom sign-in pages "OAuth Sign In" and "Email Sign In" are no longer working because
getProviders
andgetCsrfToken
are no longer exported fromnext-auth/react
.They seem to be marked with
@internal
, maybe that's why they're not exported?A type I also have used on the page which is missing is
SignInErrorTypes
which I previously imported fromnext-auth/core/pages/signin
. This was useful to show the error message if there was an error in the query parameter. Although I wouldn't need the type if I just got an object with error types mapping to the error strings.Is there any context that might help us understand?
I'm on next auth version 5.0.0-beta.3.
Does the docs page already exist? Please link to it.
https://authjs.dev/guides/basics/pages
The text was updated successfully, but these errors were encountered: