Next-auth Credentials Provider authenticated state doesn't update - next.js

I'm using next-auth 4.18.4 and can't figure out how to set up the Credential provider properly. At this point, when the user logs in, the status remains unauthenticated, and only updates to authenticated when I refresh the page. This is what I have in api/auth/[...nextauth].ts:
import NextAuth from 'next-auth'
import CredentialsProvider from 'next-auth/providers/credentials'
import { verifyPassword } from '../../../lib/auth'
import conn from '../../../lib/db'
export default NextAuth({
providers: [
CredentialsProvider({
async authorize(credentials) {
const query = `SELECT * FROM users WHERE username = $1`
const values = [ credentials.username ]
let user
try {
const result = await conn.query(query, values)
if (result.rows.length > 0) user = result.rows[0]
} catch (err) {
console.log(`Error fetching user from DB: ${err.stack}`)
throw new Error(err)
}
if (!user) throw new Error('No user found!')
const isValid = await verifyPassword(
credentials?.password,
user.password
)
if (!isValid) throw new Error('Could not log you in!')
return {
uid: user.id,
username: user.username,
profilePic: user.profile_pic
}
},
}),
],
})
This is my login page:
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { zodResolver } from '#hookform/resolvers/zod'
import { signIn } from 'next-auth/react'
import { useRouter } from 'next/router'
import Input from "../components/input"
const validationSchema = z
.object({
userName: z
.string()
.min(1, { message: 'Username is required' })
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{5,10}$/, {
message: '5-10 upper and lowercase letters, and digits',
}),
password: z
.string()
.min(5, { message: 'Between 5-10 characters' })
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{5,10}$/, {
message: 'Upper and lowercase letters, and digits',
})
})
export default function Auth() {
const {
register,
handleSubmit,
watch,
formState: { errors, isValid },
setValue
} = useForm({
mode: 'all',
resolver: zodResolver(validationSchema),
})
const router = useRouter()
async function submitHandler(data: any) {
const result = await signIn('credentials', {
redirect: false, // don't redirect if user enters wrong credentials
username: data.userName,
password: data.password
})
// console.log(result) // testing
// If the 'error' property is null (meaning log in was successful)
if (!result?.error) {
// Let's clear the input fields
setValue('userName', '')
setValue('password', '')
// And redirect the user to the main page
router.replace('/')
} else {
// If the 'error' property was false, let's print the login error
console.log(`Error: ${JSON.stringify(result.error)}`)
}
}
return (
<div className='text-white max-w-4xl mx-auto pt-10 pb-20 px-2'>
<h1 className='text-2xl text-center pb-8'>Log in</h1>
<form
onSubmit={handleSubmit(submitHandler)}
className='space-y-4 flex flex-col items-center '
>
<Input
id='userName'
type='text'
label='Username'
register={register}
registerOptions={{ required: true }}
errors={errors}
isRequired={true}
/>
<Input
id='password'
type='password'
label='Password'
register={register}
registerOptions={{ required: true }}
errors={errors}
isRequired={true}
/>
<button
type='submit'
disabled={!isValid}
className={`p-3 border-[1px] border-slate-500 rounded-md hover:enabled:bg-white hover:enabled:bg-opacity-20 disabled:cursor-not-allowed w-[90%]`}
>
{isValid ? 'Log In' : 'Please, fill the form'}
</button>
</form>
</div>
)
}
And my index page:
import { useSession } from 'next-auth/react'
export default function Home() {
const { data: session, status } = useSession()
console.log(status);
return (
<div>
<h1 className='text-2xl text-white text-center p-4'>Home Page</h1>
{status === 'authenticated' ?
(<h2 className='text-2xl text-white p-14'>Logged in!</h2>)
:
(<h2 className='text-2xl text-white p-14'>Not logged in!</h2>)
}
</div>
)
}
And the session provider in _app.ts:
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import Layout from '../components/layout'
import { SessionProvider } from 'next-auth/react'
export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
<SessionProvider session={session}>
<Layout>
<Component {...pageProps} />
</Layout>
</SessionProvider>
)
}
By the way, I forgot to add that after logging in, I can see the next-auth.session cookie being created in the browser, but still, status doesn't change until I reload.

Related

NextAuth Callbacks Not Running

I'm designing an application with Next.js and using NextAuth for authentication(using Google OAuth). In order to use other Google APIs once authenticated, I want to persist the accessToken. The accessToken gets set in the session() callback. However, it seems like the callback never runs. Could someone help me out with this? Thanks!
Here's my [...nextauth].js file
import GoogleProvider from "next-auth/providers/google"
import NextAuth from "next-auth/next"
export default NextAuth(
{
// Configure one or more authentication providers
providers: [
GoogleProvider({
clientId: PROCESS.ENV.GOOGLE_CLIENT_ID',
clientSecret: PROCESS.ENV.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code",
scope: 'openid email profile https://www.googleapis.com/auth/calendar'
},
},
callbacks: {
async session({ session, token, user }) {
session.user.id = token.id;
session.accessToken = token.accessToken;
// Not printed
console.log('In here');
return session;
},
async jwt({ token, user, account, profile, isNewUser }) {
console.log(token);
if (user) {
token.id = user.id;
}
if (account) {
token.accessToken = account?.access_token;
}
return token;
},
},
}),
// ...add more providers here
],
sercet: PROCESS.ENV.JWT_SECRET,
session: {
strategy: "jwt",
},
}
)
Here's my login component:
import React, { useState } from 'react';
import {useSession, signIn, signOut} from 'next-auth/react';
import axios from 'axios';
const Login = () => {
const x = useSession();
const {data: session} = x
const [calendar, setCalendar] = useState({});
const getCalendarData = async () => {
console.log(session.accessToken);
console.log(x);
const options = {
method: 'GET',
headers: {
Authorization: `Bearer ${session.accessToken}`,
}
};
const url = "https://www.googleapis.com/calendar/v3/calendars/primary";
const data = null
try{
data = await axios.get(url, options);
setCalendar(data);
} catch(error){
console.log(error);
}
}
if(session){
return (
<div>
<div> Welcome, {JSON.stringify(session.user)} </div>
<div>{JSON.stringify(calendar)}</div>
<div><button onClick={() => signOut()}>Sign Out</button></div>
<div><button onClick={async () => await getCalendarData()}>Get Calendar Data</button></div>
</div>
);
}
else{
return(
<div>
<div> You are not signed in </div>
<div><button onClick={() =>signIn()}> Sign in</button></div>
</div>
)
}
}

Next auth credentials

I'm trying to do a credentials auth with next-auth. I have to use a custom sign-in page and I absolutely can't make it work for approximately one entire week.
I have :
// [...nextauth.js]
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import axios from '#api/axios';
const options = {
providers: [
Providers.Credentials({
async authorize(credentials) {
const { data: user, status } = await axios.post('/users/authentication', credentials);
if (user && status === 200) {
return user;
} else {
throw new Error('error message');
}
}
})
],
pages: {
signIn: '/profil/authentication/login',
error: '/profil/authentication/login'
},
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60 // 30 days
},
debug: true
};
export default (req, res) => NextAuth(req, res, options);
and :
// profil/authentication/login
import { signOut, useSession } from 'next-auth/client';
import AuthenticationForm from '#components/auth/authenticationForm';
import Layout from '#components/layout';
const Login = () => {
const [session] = useSession();
const intl = useIntl();
return (
<Layout>
{!session && <AuthenticationForm />}
{session && (
<>
Signed in as {session.user.email}
<br />
<button onClick={signOut}>Sign out</button>
</>
)}
</Layout>
);
};
export default Login;
// authenticationForm.js
import { signIn, csrfToken } from 'next-auth/client';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import PasswordInput from '#components/auth/passwordInput';
import Button from '#components/form/button';
import TextInput from '#components/form/textInput';
const AuthenticationForm = ({ csrf }) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const handleChangeUsername = ({ target: { value } }) => setUsername(value);
const handleChangePassword = ({ target: { value } }) => setPassword(value);
const handleLogin = () => {
signIn('credentials', {
username,
password,
callbackUrl: `${window.location.origin}/profil`
})
.then((res) => {
console.log('form::res -> ', res);
router.back();
})
.catch((e) => {
console.log('form::e -> ', e);
setError('login error');
});
};
useEffect(() => {
if (router.query.error) {
setError(router.query.error);
setUsername(router.query.username);
}
}, [router]);
return (
<form onSubmit={handleLogin}>
<TextInput
name="username"
value={username}
onChange={handleChangeUsername}
/>
<PasswordInput handleChange={handleChangePassword} />
{error && <div>{error}</div>}
<Button type="submit">
connexion
</Button>
<input name="csrfToken" type="hidden" defaultValue={csrf} />
</form>
);
};
AuthenticationForm.getInitialProps = async (context) => {
return {
csrf: await csrfToken(context)
};
};
export default AuthenticationForm;
And for sure a NEXTAUTH_URL=http://localhost:3000 in .env.local.
If I go on /profil/authentication/login, I see my form and when I click connect, I always some errors like : "Failed to fetch", nothing more, or :
[next-auth][error][client_fetch_error] (2) ["/api/auth/csrf", TypeError: Failed to fetch]
https://next-auth.js.org/errors#client_fetch_error
Even if I try to delete all the csrf handling in my form and let sign-in "do it alone yea".
I'm really stuck with this lib and I most likely will change for another one but I would like to know what am I doing wrong? Is there a FULL example with custom sign-in page and errors handled on the same sign-in page. This is so basic that I can't understand why I don't find one easily.
#Tralgar
I think that problem is related to CSRF policy on your backend, if you are on localhost then localhost:3000 and localhost:2000 is like two different domains. Just make sure if you have your frontend domain in your backend cors policy (if on localhost it must be with a port)
I was able to fix the error by deleting the .next build folder and creating a new build by running npm run build

Updating user profile information with redux in firebase

I am trying to use Redux in my React application to update the user profile within my Firebase database from my react component.
This is my component:
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";
import { firestoreConnect } from "react-redux-firebase";
import { compose } from "redux";
import { editProfile } from "../../store/actions/editProfileActions";
class UserProfile extends Component {
state = {
firstName:"",
initials:"",
lastName:""
};
onChange = e => {
this.setState({
[e.target.id]: e.target.value
});
};
onSubmit = e => {
e.preventDefault();
console.log(this.state);
this.props.editProfile(this.state);
}
render() {
const { auth, profile } = this.props;
console.log(profile);
if (auth.isEmpty) return <Redirect to="/home" />;
return (
<div className="container">
<form onSubmit={this.onSubmit} className="white">
<h5 className="grey-text text-darken-3">Edit Profile</h5>
<div className="input-field">
<label htmlFor="title">First Name: {profile.firstName}</label>
<input type="text" id="firstName" onChange={this.onChange} />
</div>
<div className="input-field">
<label htmlFor="title">Initials: {profile.initials}</label>
<input type="text" id="initials" onChange={this.onChange} />
</div>
<div className="input-field">
<label htmlFor="title">Last Name: {profile.lastName}</label>
<input type="text" id="lastName" onChange={this.onChange} />
</div>
<div className="input-field">
<button className="btn black z-depth-0">Submit</button>
{ }
</div>
</form>
</div>
)
}
};
const mapStateToProps = state => {
return {
auth: state.firebase.auth,
profile: state.firebase.profile,
};
};
const mapDispatchToProps = dispatch => {
return {
editProfile: edit => dispatch(editProfile(edit))}
}
export default compose(
connect(mapStateToProps, mapDispatchToProps),
firestoreConnect([
{ collection: "profile"}
])
)(UserProfile);
The component correctly displays the current user information.
This is the action I have set up:
return async (dispatch, getState, { getFirestore, getFirebase }) => {
const firebase = getFirebase();
const user = await firebase
.auth()
.currentUser
.updateProfile({
firstName: profile.firstName
});
dispatch({ type: "EDITPROFILE_SUCCESS", user })
console.log("user = " + profile.firstName);
};
}
When I log the entered profile.firstName I get the entered data.
And my reducer:
const editProfileReducer = (state, action) => {
switch (action.type) {
case "EDITPROFILE_ERROR":
return {
...state,
editError: action.error
};
case "EDITPROFILE_SUCCESS":
return {
...state
};
default:
return state;
}
}
export default editProfileReducer;
Any idea what I am missing here?
In your reducer change the like below
case "EDITPROFILE_SUCCESS":
return {
...state,
user:action.user
};
Above is if you want to update the whole user object
If you want to change only name then
Let’s assume that profileName is in user object then
case "EDITPROFILE_SUCCESS":
return {
...state,
user:Object.assign({}, state.user, profileName:action.user.profileName)
};

signinwithemailandpassword failed: First argument "email" must be a valid string

App loads and Authentication form shows
android emulator showing error
Hi , so i'm a newbie to React and i'm following this Udemy course .. when i try to run, my app is loaded and it renders the Authentication form like a charm with no errors or warnings.
but once i try to sign in , once i hit the Login button : the scary Red Screen pops up .
i went through all the similar subjects , but none of them seem to fix my error .. i cant even get what exactly is wrong here
Here's my LoginForm.js :
import { connect } from 'react-redux';
import React, { Component } from 'react';
import { Card, CardSection, Input, Button } from './common';
import { emailChanged, passwordChanged, loginUser } from '../actions';
class LoginForm extends Component {
onEmailChange(text) {
this.props.emailChanged(text);
}
onPasswordChange(text) {
this.props.passwordChanged(text);
}
onButtonPress() {
const { email, password } = this.props;
this.props.loginUser({ email, password });
}
render() {
return (
<Card>
<CardSection>
<Input
label="Email"
placeholder="votre email ici"
onChangeText={this.onEmailChange.bind(this)}
value={this.props.email}
/>
</CardSection>
<CardSection>
<Input
secureTextEntry
label="mot de passe"
placeholder="mot de passe"
onChangeText={this.onPasswordChange.bind(this)}
value={this.props.password}
/>
</CardSection>
<CardSection>
<Button onPress={this.onButtonPress.bind(this)}>
Login
</Button>
</CardSection>
</Card>
);
}
}
const mapStateToProps = (state) => {
return {
email: state.auth.email,
password: state.auth.password
};
};
export default connect(mapStateToProps, { emailChanged, passwordChanged,
loginUser })(LoginForm);
Here's my actions creator :
import firebase from 'firebase';
import {
EMAIL_CHANGED,
PASSWORD_CHANGED,
LOGIN_USER_SUCCESS
} from './types';
export const emailChanged = (text) => {
return {
type: EMAIL_CHANGED,
paymoad: text
};
};
export const passwordChanged = (text) => {
return {
type: PASSWORD_CHANGED,
payload: text
};
};
export const loginUser = ({ email, password }) => {
return (dispatch) => {
firebase.auth().signInWithEmailAndPassword(email, password)
.then(user => {
dispatch({ type: LOGIN_USER_SUCCESS, payload: user });
});
};
};
In the emailChanged action, you are using the wrong key for the payload. Instead of using payload you are using paymoad
With this you are not updating the store and the value for the email is not valid.

vue router error with firebase auth

I tried to auth using vue.js and firebase.
and error occurs router.beforeEach function,
Anyone has any idea why it might happen?
console error
vue-router.esm.js?fe87:16 [vue-router] uncaught error during route navigation:
warn # vue-router.esm.js?fe87:16
abort # vue-router.esm.js?fe87:1904
iterator # vue-router.esm.js?fe87:1968
step # vue-router.esm.js?fe87:1717
runQueue # vue-router.esm.js?fe87:1725
confirmTransition # vue-router.esm.js?fe87:1972
transitionTo # vue-router.esm.js?fe87:1874
push # vue-router.esm.js?fe87:2181
(anonymous) # vue-router.esm.js?fe87:1960
(anonymous) # index.js?3672:44
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import addPost from '#/components/addPost'
import showPost from '#/components/showPost'
import Login from '#/components/Login'
import SignUp from '#/components/SignUp'
import firebase from 'firebase'
Vue.use(Router)
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'app',
component: showPost
},
{
path: '/add',
component: addPost,
meta: {
requiresAuth: true
}
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/signup',
name: 'SignUp',
component: SignUp
}
]
})
router.beforeEach((to, from, next) => {
let currentUser = firebase.auth().currentUser;
let requiresAuth = to.matched.some(record => record.meta.requiresAuth);
if (requiresAuth && !currentUser) next('/login')
else if (!requiresAuth && currentUser) next('/')
else next()
})
export default router
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import VueFire from 'vuefire'
import firebase from 'firebase'
Vue.use(VueFire)
Vue.config.productionTip = false
let app;
firebase.auth().onAuthStateChanged(function(user) {
if(!app) {
app = new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
}
})
App.vue
<template>
<div id="app">
<app-header></app-header>
<router-view></router-view>
<button #click="logout">Logout</button>
</div>
</template>
<script>
import header from './components/header'
import firebase from 'firebase'
export default {
name: 'app',
components: {
'app-header': header
},
methods: {
logout: function() {
firebase.auth().signOut().then(() => {
this.$router.replace('login')
})
}
}
}
</script>
Login.vue
import firebase from 'firebase'
import db from '../firebaseInit'
const postRef = db.ref('posts')
export default {
name: 'login',
data: function() {
return {
email: '',
password: ''
}
},
methods: {
signIn: function() {
firebase.auth().signInWithEmailAndPassword(this.email, this.password).then(
(user) => {
this.$router.replace('/')
},
(err) => {
alert('Oops ' + err.message)
}
);
}
}
}
</script>
addPost.vue
<template>
<div id="add-blog">
<h2>Add a New Post</h2>
<form v-if="!submitted">
<label>Title:</label>
<input type="text" v-model="newPost.title" required />
<p>{{ getDate }}</p>
<label for="">Content:</label>
<textarea v-model.trim="newPost.content"></textarea>
<div id="checkboxes">
<p>Categories:</p>
<label>Vue.js</label>
<input type="checkbox" value="vue" v-model="newPost.categories" />
<label>CSS Magic</label>
<input type="checkbox" value="css" v-model="newPost.categories" />
</div>
<label>Author:</label>
<select v-model="newPost.author">
<option v-for="author in authors">{{ author }}</option>
</select>
<button #click.prevent="addPost">Add Post</button>
</form>
<div v-if="submitted">
<p>Congraturation!</p>
</div>
<div id="preview">
<h3>Preview Post</h3>
<h4>Title {{ newPost.title }}</h4>
<h4>Content </h4>
<p style="white-space: pre">{{ newPost.content }}</p>
<ul>
<li v-for="category in newPost.categories">{{ category }}</li>
</ul>
<p>{{ newPost.author }}</p>
</div>
</div>
</template>
<script>
import db from '../firebaseInit'
const postRef = db.ref('posts')
export default {
data() {
return {
newPost: {
date: '',
title: '',
author: '',
content: '',
categories: []
},
authors: ['Naeun', 'Raphael'],
submitted: false,
items: []
}
},
methods: {
addPost: function() {
postRef.push(this.newPost)
this.newPost.date = '',
this.newPost.title = '',
this.newPost.author = '',
this.newPost.content = '',
this.newPost.categories = ''
},
removePost: function() {
postRef.child(post['.key']).remove()
}
},
computed: {
getDate: function() {
const toTwoDigits = num => num < 10 ? '0' + num : num;
let today = new Date();
let year = today.getFullYear();
let month = toTwoDigits(today.getMonth() + 1);
let day = toTwoDigits(today.getDate());
return this.newPost.date = `${year}-${month}-${day}`;
}
}
}
</script>
Make sure that the next function is called exactly once in any given pass through the navigation guard. It can appear more than once, but only if the logical paths have no overlap, otherwise the hook will never be resolved or produce errors.
Your else if condition else if (!requiresAuth && currentUser) next('/') violates this rule.
Change your route guarding logic
// check if route requiresAuth
router.beforeEach((to, from, next) => {
if (to.matched.some(rec => rec.meta.requiresAuth)) {
const user = firebase.auth().currentUser;
// check auth state of user
user ? next() : next('/login') // user not signed in, route to login
} else {
next(); // route does not require auth
}
});
https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
Hope this helps.

Resources