I am trying to implement next-auth with my next js app with the Credentials provider. However, everytime I fail a login, it keeps trying to access the /api/auth/error
route. I am trying to custom handle an error by staying on the login page and adding a param at the end of the url like /login?status=Failed
. What am I doing wrong that it keeps trying to get to the /api/auth/error
route instead of following my flow?
My code:
/pages/api/auth/[...nextauth].js
import { authenticate } from "api/login";
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
export default NextAuth({
session: {
strategy: 'jwt'
},
providers: [
CredentialsProvider({
id: 'credentials',
name: 'Credentials',
credentials: {
username: { type: 'text', label: 'Username'},
password: { type: 'text', label: 'Password' }
},
async authorize(credentials, req) {
const res = await authenticate(credentials); // custom function that returns an object with a jwt if auth is successful
if(res.status !== 200) {
return null;
}
return res.user;
}
})
],
pages: {
signIn: '/login'
},
callbacks: {
async signIn(user) {
console.log('user: ' user);
if (!user) return '/login?status=fail';
return '/'
}
}
});
login page (i am aware this is not best practice)
/pages/login.js
import { useState } from 'react';
import { signIn } from 'next-auth/react';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';
export default function Login() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const onSubmit = async () => {
signIn('credentials', {
username: username,
password: password,
redirect: false,
})
.then(res => {
console.log(res);
})
.catch(err => {
console.error(err);
});
};
return (
<Container>
<Row>
<Col lg={6} className="offset-lg-3">
<Form>
<Row>
<Col>
<Form.Group controlId="loginUsername">
<Form.Label>Username</Form.Label>
<Form.Control
type="text"
onChange={(e) => setUsername(e.target.value)}
value={username} />
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<Form.Group controlId="loginPassword">
<Form.Label>Password</Form.Label>
<Form.Control
type="text"
onChange={(e) => setPassword(e.target.value)}
value={password} />
</Form.Group>
</Col>
</Row>
<Row className="pt-3">
<Col lg={4}>
<Button onClick={onSubmit}>Login</Button>
</Col>
</Row>
<Row>
<Col>
</Col>
</Row>
</Form>
</Col>
</Row>
</Container>
)
}
_app.js
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { SessionProvider } from 'next-auth/react';
function MyApp({
Component,
pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
}
export default MyApp;
and additionally, here's a protected page i setup
import React from 'react';
import { getSession } from 'next-auth/react';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
export default function Home() {
return (
<Container fluid>
<Row>
<Col>
<h1>Hello World</h1>
</Col>
</Row>
</Container>
);
}
export async function getServerSideProps(context) {
const session = await getSession({ req: context.req });
if (!session) {
return {
redirect: {
destination: '/login',
permanent: false
}
}
}
return {
props: { session }
}
}
CodePudding user response:
you need to specify the error page too, and since you want to show the error on the login page it should be like this:
pages: {
signIn: '/login',
error: '/login'
},
you can try this approach I got from this blogpost to customize the login page to handle different login errors:
// pages/login.js
import { useState } from 'react';
import { signIn } from 'next-auth/react';
import { useRouter } from 'next/router';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';
export default function Login() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const { error } = useRouter().query;
const onSubmit = async () => {
signIn('credentials', {
username: username,
password: password,
redirect: false,
})
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});
};
return (
<Container>
<Row>
<Col lg={6} className="offset-lg-3">
{/* Error message */}
<Row>
<Col>{error && <SignInError error={error} />}</Col>
</Row>
<Form>
<Row>
<Col>
<Form.Group controlId="loginUsername">
<Form.Label>Username</Form.Label>
<Form.Control
type="text"
onChange={(e) => setUsername(e.target.value)}
value={username}
/>
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<Form.Group controlId="loginPassword">
<Form.Label>Password</Form.Label>
<Form.Control
type="text"
onChange={(e) => setPassword(e.target.value)}
value={password}
/>
</Form.Group>
</Col>
</Row>
<Row className="pt-3">
<Col lg={4}>
<Button onClick={onSubmit}>Login</Button>
</Col>
</Row>
<Row>
<Col></Col>
</Row>
</Form>
</Col>
</Row>
</Container>
);
}
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 }) => {
const errorMessage = error && (errors[error] ?? errors.default);
return <div>{errorMessage}</div>;
};
you can customize how the error is shown to your needs