Home > Back-end >  next-auth keeps redirecting on error with credentials provider
next-auth keeps redirecting on error with credentials provider

Time:09-15

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

  • Related