Next auth. Please
Answered
Sun bear posted this in #help-forum
Sun bearOP
i want to get errors from next auth when user signs in with oauth
Answered by ncls.
So the issue is that
To fix this, make the following changes in your
pathname does not include URL parameters. So when redirecting the user to their locale, your middleware dumps all query parameters.To fix this, make the following changes in your
middleware function:// add this line where you also define your `pathname` variable:
const searchParams = request.nextUrl.search;
// edit your redirect line like this:
return NextResponse.redirect(
new URL(`/${locale + pathname + searchParams}`, request.url);
);67 Replies
Sun bearOP
i spent whole 2 days for simple thing
i just want the method to get oauth erros on custom login page
i dont belive nobody needed that
or i think nobody builds pages by next auth
this not working
Sun bearOP
i give up
@Sun bear i want to get errors from next auth when user signs in with oauth
register the signin page as an error page inside your NextAuth configuration and then get the error from the URL parameter
Sun bearOP
there is nothing in url
actually
Sun bearOP
@ncls.
Can you share your NextAuth config please?
Sun bearOP
import { prisma } from "@/lib/prisma";
import { compare } from "bcryptjs";
import type { NextAuthOptions } from "next-auth";
import GithubProvider from "next-auth/providers/github";
import CredentialsProvider from "next-auth/providers/credentials";
import GoogleProvider from "next-auth/providers/google";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
export const authOptions: NextAuthOptions = {
session: {
strategy: "jwt",
},
secret: process.env.NEXTAUTH_SECRET as string,
adapter: PrismaAdapter(prisma),
providers: [
CredentialsProvider({
name: "Sign in",
credentials: {
email: { label: "Email", type: "email", placeholder: "jsmith" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
const { email, password } = credentials as {
email: string;
password: string;
};
if (!credentials || !email || !password) {
throw new Error("Invalid credentials");
}
const user = await prisma.user.findUnique({
where: {
email: email,
},
});
if (
!user ||
!user.password ||
!(await compare(password, user.password))
) {
throw new Error("Email or password is incorrect");
}
return {
id: user.id,
email: user.email,
};
},
}),
GithubProvider({
clientId: process.env.GITHUB_ID as string,
clientSecret: process.env.GITHUB_SECRET as string,
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
}),
],
pages: {
signIn: "/login",
error: "/login",
signOut: "/login",
},
};import Link from "next/link";
import LoginForm from "./form";
import { redirectIfAuthenticated, signOutIfBlocked } from "@/lib/protected";
export default async function Login({
params: { lang },
}: {
params: { lang: string };
}) {
await redirectIfAuthenticated();
// get query error
return (
<div>
<div className="p-5 flex flex-col gap-3 items-start">
<h2 className="font-bold text-2xl">Welcome back!</h2>
<p className="text-tsecondary">
{"Don't have an account? "}
<Link className="underline" href="/register">
Create an account
</Link>
</p>
<LoginForm />
</div>
</div>
);
}next/router doesnt work in new nextjs
Yeah, use
next/navigation insteadSun bearOP
but there is no error in next/navigation query
or in useSearchParams
Because there is no error in your URL
We gotta find out why
Sun bearOP
because when you sign in google
its redirected to google page then from google page
redirected to some link like /api/auth/providers/google
i dont remember
That's fine so far
It gets a token from Google, then uses that token to get the user data in the callback
Sun bearOP
i dont understand why signIn function doesnt have error handler itself
yes ans as we see in network page it actually gets this error in url
Yup
Let me test some stuff
Sun bearOP
okay
What does
await redirectIfAuthenticated(); do?Sun bearOP
export async function redirectIfAuthenticated() {
const session = await getServerSession(authOptions);
session && redirect("/");
}Can you comment that line out and test?
Because something is redirecting you
And that is why you are not getting passed the URL parameters
Sun bearOP
yes and its same
Sun bearOP
"use client";
import { AiOutlineLoading3Quarters } from "react-icons/ai";
import React, { useState } from "react";
import { Formik, Form, Field, ErrorMessage } from "formik";
import { signIn } from "next-auth/react";
import {
useParams,
usePathname,
useRouter,
useSearchParams,
} from "next/navigation";
import { FaGithub, FaGoogle } from "react-icons/fa";
const CALLBACK_URL = "/";
const LoginForm = () => {
const [error, setError] = useState<null | string>(null);
const [loading, setLoading] = useState(false);
const router = useRouter();
const validateForm = (values: { email: string; password: string }) => {
const errors: any = {};
if (!values.email) {
errors.email = "Required";
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) {
errors.email = "Invalid email address";
}
if (!values.password) {
errors.password = "Required";
}
return errors;
};
const handleSubmit = (
values: {
email: string;
password: string;
},
{ setSubmitting }: any
) => {
setLoading(true);
setError(null);
const { email, password } = values;
signIn("credentials", {
redirect: false,
email,
password,
})
.then((res) => {
router.push(CALLBACK_URL);
})
.catch((err) => {
setSubmitting(false);
setLoading(false);
setError(err.message);
});
};
return (
<>
{error && <div className="text-red-500">{error}</div>}
<Formik
initialValues={{ email: "", password: "" }}
validate={validateForm}
onSubmit={handleSubmit}
>
{({ isSubmitting }) => (
<Form className="flex flex-col gap-3 items-start max-w-xs w-full">
<Field
className="border border-secondary p-3 bg-transparent outline-none w-full rounded-sm"
placeholder="Please enter your email..."
type="email"
name="email"
/>
<ErrorMessage name="email" component="div" />
<Field
className="border border-secondary p-3 bg-transparent outline-none w-full rounded-sm"
placeholder="Please enter your password..."
type="password"
name="password"
/>
<ErrorMessage name="password" component="div" />
<button type="submit" disabled={isSubmitting}>
{loading ? (
<AiOutlineLoading3Quarters className="animate-spin text-2xl" />
) : (
"Sign In"
)}
</button>
</Form>
)}
</Formik>
{/* or sign in with google or github */}
<button
type="button"
onClick={() => {
signIn("google", {
redirect: false,
});
}}
>
<FaGoogle />
google
</button>
<button
type="button"
onClick={() => {
signIn("github", {
redirect: false,
});
}}
>
<FaGithub />
github
</button>
</>
);
};
export default LoginForm;Ik
And you can see in the response headers that it redirects you without parameters
So seems the be the localization
Can you show me how that's implemented?
Sun bearOP
import { NextRequest, NextResponse } from "next/server";
import { match } from "@formatjs/intl-localematcher";
import Negotiator from "negotiator";
import { defaultLocale, locales } from "./locals";
function getLocale(request: NextRequest) {
const headers = new Headers(request.headers);
const acceptLanguage = headers.get("accept-language");
if (acceptLanguage) {
headers.set("accept-language", acceptLanguage.replaceAll("_", "-"));
}
const headersObject = Object.fromEntries(headers.entries());
const languages = new Negotiator({
headers: headersObject,
}).languages();
return match(languages, locales, defaultLocale);
}
export function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
const pathnameIsMissingLocale = locales.every(
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
);
if (pathnameIsMissingLocale) {
const locale = getLocale(request);
return NextResponse.redirect(
new URL(`/${locale}/${pathname}`, request.url)
);
}
}
export const config = {
matcher: ["/((?!_next|api|favicon.ico).*)"],
};Alright, one sec
Sun bearOP
why is there
two /
i removed api from matcher
and now there is 404 page
@Sun bear js
import { NextRequest, NextResponse } from "next/server";
import { match } from "@formatjs/intl-localematcher";
import Negotiator from "negotiator";
import { defaultLocale, locales } from "./locals";
function getLocale(request: NextRequest) {
const headers = new Headers(request.headers);
const acceptLanguage = headers.get("accept-language");
if (acceptLanguage) {
headers.set("accept-language", acceptLanguage.replaceAll("_", "-"));
}
const headersObject = Object.fromEntries(headers.entries());
const languages = new Negotiator({
headers: headersObject,
}).languages();
return match(languages, locales, defaultLocale);
}
export function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
const pathnameIsMissingLocale = locales.every(
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
);
if (pathnameIsMissingLocale) {
const locale = getLocale(request);
return NextResponse.redirect(
new URL(`/${locale}/${pathname}`, request.url)
);
}
}
export const config = {
matcher: ["/((?!_next|api|favicon.ico).*)"],
};
So the issue is that
To fix this, make the following changes in your
pathname does not include URL parameters. So when redirecting the user to their locale, your middleware dumps all query parameters.To fix this, make the following changes in your
middleware function:// add this line where you also define your `pathname` variable:
const searchParams = request.nextUrl.search;
// edit your redirect line like this:
return NextResponse.redirect(
new URL(`/${locale + pathname + searchParams}`, request.url);
);Answer
Here's a screenshot of the structure of the
request.nextUrl object to better understand why this error occurred and how we fix itSun bearOP
yes i understand
thanks
@Sun bear why is there
Oh, btw the two slashes are there because
pathname already includes a slash. I edited my answer accordinglySun bearOP
okay
and i have one question if you have time
const errors = {
Signin: "Try signing with a different account.",
OAuthSignin: "Try signing with a different account.",
OAuthCallback: "Try signing with a different account.",
OAuthCreateAccount: "Try signing with a different account.",
EmailCreateAccount: "Try signing with a different account.",
Callback: "Try signing with a different account.",
OAuthAccountNotLinked:
"To confirm your identity, sign in with the same account you used originally.",
EmailSignin: "Check your email address.",
CredentialsSignin:
"Sign in failed. Check the details you provided are correct.",
default: "Unable to sign in.",
};
const SignInError = ({ error }: any) => {
const errorMessage =
error && (errors[error as keyof typeof errors] ?? errors.default);
return <div className="text-red-500">{errorMessage}</div>;
};is this right way?
It's not a wrong way so you should be fine using this
Sun bearOP
okay thanks
it would be good if signIn function itself had error handling functionallity
like credentials signIn works
thanks a lot
@Sun bear it would be good if signIn function itself had error handling functionallity
Since you are getting redirected, your website can not persist the state so I think it's not possible to do that. The approach NextAuth makes currently is the best and easiest one