Next Js combined with an external REST API Authentication and atuhorization - javascript

I have already an application built with Node Js and express , also I have a front end using create-react-app with redux , but now I would like to move it to next Js, but I've got stuck because I do not the right way to authenticate and authorize using my rest Full API, I want to mention that my API already handle this using JWT (saving it within a cookie)

Next-Auth is a go-to in such a case. The following example shows how to get started with password authentication.
Create a file called /pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
const options = {
providers: [
Providers.Credentials({
// The name to display on the sign in form (e.g. 'Sign in with...')
name: 'Email and Password',
credentials: {
username: { label: "Username", type: "text", placeholder: "jsmith" },
password: { label: "Password", type: "password" }
},
authorize: async (credentials) => {
// Add logic here to look up the user from the credentials supplied eg from db or api
const user = { id: 1, name: 'J Smith', email: 'jsmith#example.com' }
if (user) {
// Any object returned will be saved in `user` property of the JWT
return Promise.resolve(user)
} else {
// If you return null or false then the credentials will be rejected
return Promise.resolve(null)
// You can also Reject this callback with an Error or with a URL:
// return Promise.reject(new Error('error message')) // Redirect to error page
// return Promise.reject('/path/to/redirect') // Redirect to a URL
}
}
}),
],
}
export default (req, res) => NextAuth(req, res, options)
Then on your component code:
import React from 'react'
import {
signIn,
signOut,
useSession
} from 'next-auth/client'
export default function myComponent() {
const [ session, loading ] = useSession()
return <>
{!session && <>
Not signed in <br/>
<button onClick={signIn}>Sign in</button>
</>}
{session && <>
Signed in as {session.user.email} <br/>
<button onClick={signOut}>Sign out</button>
</>}
</>
}
This would easily get you started, if you shared more of the auth routes you have I can advise more.

Related

custom verify request page for next auth is not loading after sign in

using next auth with custom verify request page but it won't load after sign in (i.e. the page hangs or just stays on the same page it was already on) because of the following error, anyone know the reasoning?
API resolved without sending a response for /api/auth/verify-request?provider=email&type=email, this may result in stalled requests.
/api/auth/verify-request.tsx
const Verify = () => {
return (
<div className="">
<div className="text-3xl">Check your email!!!!!!!!</div>
</div>
);
};
[...nextauth].js
export default NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
// Passwordless / email sign in
EmailProvider({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
maxAge: 3600,
}),
],
secret: process.env.SECRET,
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
return true;
},
async redirect({ url, baseUrl }) {
return baseUrl;
},
async session({ session, user, token }) {
session.user.id = user.id;
return Promise.resolve(session);
},
async jwt({ token, user, account, profile, isNewUser }) {
return token;
},
},
pages: {
verifyRequest: "/auth/verify-request", // (used for check email message)
},
});
login form, tried both submitting form with csrfToken as well as tried signIn function by next-auth/react
signIn("email", { email: inputEmail });
Solved. the custom verify page had to be outside the api folder

client fetch error on live website built using next.js

Hi I have tried all things possible to find out what could be causing above error on my live website built using NEXTJS.
I have noticed that this error happens whenever I reload the website.
I also notice that whenever I try to login using userName and password, I am able to do that without any errors in local host and also using https://codesandbox.io. But on the live site I get a server error "problem with the server configuration.".
when I scroll further on my developer tools I find the following additional information.
Unexpected token < in JSON at position 0 {error: {…}, path: "session", message: "Unexpected token < in JSON at position 0"
I have added the following environment variables in vercel
NEXTAUTH_URL = https://****.vercel.app/
MONGODB_URI = mongodb+srv://****#cluster0.9kc5p.mongodb.net/*****?retryWrites=true&w=majority
my [...nextauth].js file is as below
import NextAuth from "next-auth";
import CredentialsProviders from "next-auth/providers/credentials";
import { verifyPassword } from "../../../lib/hashedPassword";
import clientPromise from "../../../lib/mongodb";
export default NextAuth({
session: {
strategy: "jwt"
} /* check other providers you may add database etc */,
providers: [
CredentialsProviders({
/* authorize will be called when it receives incoming login req */
async authorize(credentials) {
const client = await clientPromise;
const db = client.db();
/* check if we have user or email */
const usersCollection = await db.collection("users");
const user = await usersCollection.findOne({
$or: [
{ email: credentials.email },
{ userName: credentials.userName }
]
});
if (!user) {
throw new Error("No user found");
}
const isvalid = await verifyPassword(
credentials.password,
user.password
);
if (!isvalid) {
throw new Error("password is invalid");
}
return {
email: user.email
};
}
})
]
});
my login page is as below
import Button from "../../UI/Button/Button";
import Input from "../../UI/Input/Input";
import Card from "../../UI/card/Card";
import classes from "./Login.module.css";
import Link from "next/link";
import { useForm } from "react-hook-form";
import { signIn, getSession } from "next-auth/react";
import { useRouter } from "next/router";
const Login = () => {
const route = useRouter();
const {
register,
handleSubmit,
formState: { errors }
} = useForm();
const submittedFormHandler = async (userInputs) => {
const result = await signIn("credentials", {
redirect: false,
email: userInputs.userNameEmail,
userName: userInputs.userNameEmail,
password: userInputs.password
}); /* result will always resolve */
if (!result.error) {
route.replace("/");
}
};
return (
<>
<Card className={classes.login}>
<form onSubmit={handleSubmit(submittedFormHandler)}>
<Input
htmlFor="userNameEmail"
id="userNameEmail"
label="UserName or Email"
input={{
type: "text",
...register("userNameEmail", { required: true})
}}
></Input>
<span className={classes.spanning}>
{errors.userName &&
"Enter userName or Email at least four characters"}
</span>
<Input
htmlFor="password"
id="password"
label="Enter Password"
input={{
type: "password",
...register("password", { required: true, minLength: 8 })
}}
></Input>
<span className={classes.spanning}>
{errors.password && "password should be at least 8 characters"}
</span>
<div className={classes.password}>
<Button type="submit">Submit</Button>
<Link href="/ForgotPassword">Forgot Password ?</Link>
</div>
<Link href="/NewUser" className={classes.link}>
Create Account New User
</Link>
</form>
</Card>
</>
);
};
export async function getServerSideProps(context) {
const session = await getSession({
req: context.req
}); //returns session obj or null
if (session) {
return {
redirect: {
destination: "/",
permanent: false
}
};
}
return {
props: { session }
};
}
export default Login;
what could be the problem? please assist
I faced the same problem, but i was using mysql as database and i didn't use the auth file middleware that suggest next-auth to handle the providers, instead i created a separated file to handle sequelize (in your case will be the orm with the database you're using).
I fixed it adding dialectModule to the propertys of the class Sequelize
const db = new Sequelize(`${process.env.DB_URI}`, {
database: process.env.DB_NAME,
logging: false,
dialect: "mssql",
dialectModule: require("mysql2"),
});
I also have this problem. They said on the doc that make sure you define NEXTAUTH_URL variable correctly. If you use Vercel to host, then the content of the variable should be only the url without quote. For example, https:project.vercel.app.
If not solved, try changing the [...nextauth].ts file to a more simple version. I got this error when I tried doing things with database in the callback (mongodb in my case) like this
async jwt({ token }) {
let role:Role = 'user'
if (!token.email) throw Error('no email provided with token')
let user = await getUser(token.email)
if (user?.isAdmin) role = 'admin'
return {...token, role}
}
After removing this, my problem is solved. In your case, you could try removing anything that deals with the database.
After I got that working, I added this callback function instead
async jwt({ token }) {
let role:Role = 'user'
if (!token.email) throw Error('no email provided with token')
const client = await clientPromise
const collection = client.db().collection('users')
const user = await collection.findOne({email:token.email})
if (user?.isAdmin) role = 'admin'
return {...token, role}
}
The only difference is that the first one use mongoose, and the second one doesn't. The approach of the second one is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
Disclaimer: I don't know why it worked.

API resolved without sending a response for /api/auth/callback/credentials, this may result in stalled requests

When i try to login using Talend API Tester, getting this into terminal:
API resolved without sending a response for /api/auth/callback/credentials, this may result in stalled requests.
Besides, here is the image of Request:
I followed this: Next-Auth.js -> Rest API
Why I'm getting this kind of response & warning?
Below is the [..nextauth.js] file's code. What's wrong with my code?
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
import prisma from '../../../lib/prisma'
const options = {
providers: [
Providers.Credentials({
name: 'Credentials',
credentials: {
email: { label: "Email", type: "email", placeholder: "something#example.com" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
const {email, password} = credentials
const user = await prisma.user.findFirst({ where: { email, password } })
console.log(user);
// If no error and we have user data, return it
if (user) {
return user
}
// Return null if user data could not be retrieved
return null
}
})
]
}
export default async function handle(req, res) {
console.log('I see');
NextAuth(req, res, options)
}
Note: Actually I don't want to send csrfToken in the body. Only email & password to the endpoint. Please have a look into my this question How to do authentication using NextAuth.js without login page (using postman)

Vuex getter authenticated returns false in route guard. I assume it evaluates to false because at the time of calling autoLogin hasn't executed yet

So I have a /login route that renders the login view and I'm trying to make it so that if the user is already logged in, he gets redirected to another route. The problem is that when I type the url www.example.com/login, authenticated evaluates to false for some reason. This is how my code works:
Login Url:
{
path: '/login',
name: 'login',
component: Login,
beforeEnter: (to, from, next) => {
if (store.getters.authenticated) {
next({ name: "adminOrders" })
} else {
next()
}
}
},
Vuex authentication store:
import router from '../router'
import axios from 'axios'
const authentication = {
state: {
user: null
},
mutations: {
setUser(state, user){
state.user = user
localStorage.setItem('userId', user.id);
localStorage.setItem('username', user.username);
localStorage.setItem('token', user.token);
}
},
actions: {
autoLogin({commit, dispatch}){
const userId = localStorage.getItem('userId')
const username = localStorage.getItem('username')
const token = localStorage.getItem('token')
if (!token) {
return
}
let user = {
id: userId,
username: username,
token: token
}
commit('setUser', user)
axios.interceptors.request.use(function (config) {
config.headers.Authorization = 'Bearer ' + token
return config
});
},
},
getters: {
authenticated: state => {
return state.user ? true : false
}
}
}
export default authentication
And I call the autoLogin on App.vue mounted like this:
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
mounted(){
this.$store.dispatch('autoLogin');
}
}
</script>
beforeEnter is called before App is mounted, because router gets instantiated as soon as App is created.
Since you call autoLogin action from App's mounted() it's actually run after the router reads the getter from the store.
However, you could call an async action from beforeEnter, which would return whether or not there is a token in localStorage.
At a minimum, here's what would work:
// routes:
beforeEnter: async (to, from, next) => {
const hasToken = await store.dispatch('hasToken');
next(hasToken ? { name: 'adminOrders' } : undefined);
}
// store:
actions: {
hasToken() {
return !!localStorage.getItem('token')
}
// ...
}
Make sure you wipe the token out from localStorage when you get a 401 error in your axios interceptors (basically means "token expired"). If you do not clear the token from localStorage before trying to go to /login (which is what usually happens on 401), the beforeEnter will redirect to adminOrders, adminOrders will attempt to load data, data calls will return 401 as token is expired and you end up in a loop.
Alternatively, you could just get a new token on 401's and update localStorage.

ECONNREFUSED 127.0.0.1:80 when trying to sign in using next-auth

I use next-auth.js library to create authentication system for Next.js my application.
Everything works fine on my local computer, but after deploying website to Vercel, my sign in function doesn't work.
The sign in page (/pages/signin) loads fine, i submit credentials and i get an error like this:
https://projectname-git-branch.username.now.sh/api/auth/error?error=Error%3A%20connect%20ECONNREFUSED%20127.0.0.1%3A80.
I found many articles that error is about to wrong baseURL configured for axios, but i have many axios requests everywhere in code (for example i get data from firebase after app loads) and it works without any problem.
I console logged environment variable where i set baseURL property for axios and it's fine.
// _app.js
import axios from 'axios';
axios.defaults.baseURL = process.env.FIREBASE_APIURL;
console.log('axios-baseurl', axios.defaults.baseURL) // it prints right url
More informations about this problem i wrote there: #846
I really don't know what to do. I don't get any information about where this error happens, which file and line number, what request in my code.
Below i put my whole [...nextauth.js] file and sign in form. Maybe there is something what i should to configure better there.
// [...nextauth.js]
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
import axios from 'axios';
axios.defaults.baseURL = process.env.FIREBASE_APIURL;
console.log('axios-baseurl', axios.defaults.baseURL)
const sha1 = require('crypto-js/sha1');
const md5 = require('crypto-js/md5');
export default (req, res) =>
NextAuth(req, res, {
providers: [
Providers.Credentials({
name: 'Credentials',
credentials: {
phone: { label: "Numer telefonu", type: "text" },
password: { label: "Haslo", type: "password" }
},
authorize: async (loginData) => {
const { csrfToken, phone, password } = loginData;
// checking if there is account with these credentials
let res = await login({
phone,
password: sha1(md5(password)).toString()
})
// 200 = OK
if(res.status == 200){
// check if account was activated
if(res.data.info.activated == "true"){
// collect account data
const user = {
phone,
...res.data.info
}
return Promise.resolve(user);
}
else{
// account is not activated by sms code
return Promise.reject(new Error("AccountNotActivated"));
}
}
else {
// wrong credentials
return Promise.reject(new Error("WrongCredentials"));
}
}
})
],
callbacks: {
jwt: async (token, user, account, profile, isNewUser) => {
user && (token.user = user);
return Promise.resolve(token)
},
session: async (session, user, sessionToken) => {
session.user = user.user;
return Promise.resolve(session)
}
},
pages: {
signIn: '/signin'
},
site: process.env.NEXTAUTH_URL || "localhost:3000",
debug: true
})
// function which sends login request to rest api and returns result
// https://stackoverflow.com/q/64244115
const login = async data => await axios.post('/checkCredentials', data);
<!-- /pages/signin.js -->
<form action="/api/auth/callback/credentials" method="post">
<input name='csrfToken' type='hidden' defaultValue={csrfToken} />
<label>
Numer telefonu: <br />
<input name='phone' type='text' required autoComplete='off' />
</label>
<br />
<label>
Hasło: <br />
<input name='password' type='password' required autoComplete='off' />
</label>
<br />
<br />
<input type="submit" value="Zaloguj" />
</form>
You'll need to set environment variables for production use:
NEXTAUTH_URL (e.g. NEXTAUTH_URL='https://example.com'
https://next-auth.js.org/configuration/options#nextauth_url
and
NEXTAUTH_SECRET (e.g. NEXTAUTH_SECRET='MYGENERATEDOPENSSLSECRET')
documentation # https://next-auth.js.org/configuration/options#nextauth_secret
You can generate a secret on the command line (only one possible way)
openssl rand -base64 32
Add both variables to your vercel deployment:
https://vercel.com/docs/concepts/projects/environment-variables

Categories