I am trying to learn next-auth, I finally got the login to work but when I pass the id from the user in the database it doesn't show up in the session only the values that I provided null to show up
import NextAuth from 'next-auth'
import CredentialProvider from 'next-auth/providers/credentials'
import connectMongo from '../../../lib/db'
import User from '../../../models/userModel'
export const authOptions = {
session: {
strategy: 'jwt',
},
providers: [
CredentialProvider({
async authorize(credentials) {
await connectMongo()
const u = await User.findOne({ username: credentials.username })
console.log(u.id)
if (u) {
return {
id: u.id,
name: null,
email: null,
image: null,
}
}
// login failed
return null
},
}),
],
}
export default NextAuth(authOptions)
login is successful with no errors but the id doesn't show up in the props.
{email: null, image: null, name: null} is all that is returned.
If I don't add null to all those keys it returns this error SerializableError: Error serializing .session.user.name returned from getServerSideProps in "/authenticated".
Reason: undefined cannot be serialized as JSON. Please use null or omit this value.
import { authOptions } from './api/auth/[...nextauth]'
import { unstable_getServerSession } from 'next-auth/next'
function Authenticated(props) {
return <div>Authenticated</div>
}
export default Authenticated
export async function getServerSideProps(context) {
const session = await unstable_getServerSession(
context.req,
context.res,
authOptions
)
if (!session) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
return {
props: {
session,
},
}
}
get session in getServerSideProps also returns an empty object after successful login with no errors
import { getSession } from 'next-auth/react'
function Authenticated(props) {
console.log(props)
return <div>Authenticated</div>
}
export default Authenticated
export async function getServerSideProps(context) {
const session = await getSession({ req: context.req })
if (!session) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
return {
props: { session },
}
}
Related
I'm using next-auth/discord however when using the session callback to set a user id to the session it does not set the property.
[...nextauth].js
import NextAuth from "next-auth/next";
import DiscordProvider from "next-auth/providers/discord";
export default NextAuth({
providers: [
DiscordProvider({
...
session: {
strategy: "jwt",
...
},
callbacks: {
async session({ session, user }) {
session.user.id = user.id;
return session;
}
}
})
]
});
/api/page.js
import { getSession } from 'next-auth/react';
export default async function handler(req, res) {
const session = await getSession({ req });
console.log(session);
}
This logs:
{
user: {
name: ...,
email: ...,
image: ...
},
expires: ...
}
With no user.id property.
Fixed it, callbacks should have been in NextAuth object. Also callbacks shouldve been:
async jwt({ token, user }) {
if (user) {
token.id = user.id
}
return token
},
async session({ session, token }) {
session.user.id = token.id
return session
}
I have added authorization to my Nuxt app, but something is wrong. When i enter wrong password or email, I am still redirected to the main page of the application, although I have to stay on the authorization page and try to log in again.
Here is my code:
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut
} from 'firebase/auth'
export default {
data() {
return {
snackBar: false,
snackBarText: 'No Error Message',
auth: {
email: '',
password: ''
}
}
},
methods: {
login() {
let that = this
this.$fire.auth.signInWithEmailAndPassword(this.auth.email, this.auth.password)
.catch(function (error) {
console.log(error.message);
that.snackBarText = error.message
that.snackBar = true
// $nuxt.$router.push('/login')
}).then((user) => {
console.log(user);
$nuxt.$router.push('/')
})
}
}
}
middleware:
export default function ({ app, route, redirect }) {
if (route.path !== '/login') {
// we are on the protected route
if (!app.$fire.auth.currentUser) {
// take them to sign in in a page
return redirect('/login')
}
} else if (route.path === '/login') {
if (!app.$fire.auth.currentUser) {
// leave them on the sign in page
} else {
return redirect('/')
}
}
}
store:
const state = () => ({
user: null,
};
const mutations = {
SET_USER(state, user) {
state.user = user
},
}
const actions = {
async onAuthStateChangedAction(context, { authUser, claims }) {
if (!authUser) {
context.commit('SET_USER', null)
this.$router.push({
path: '/login'
})
} else {
const { uid, email } = authUser;
context.commit('SET_USER', {
uid,
email
})
}
}
}
const getters = {
getUser(state) {
return state.user
}
}
export default {
state,
actions,
mutations,
getters,
}
Form for authorization is in component popup, which is sent to page login.vue
Having some trouble with setting up nextauth v4. Getting this error:
Client fetch error, Unexpected end of JSON input {error: {…}, path:
'session', message: 'JSON.parse: unexpected end of data at line 1
column 1 of the JSON data'}.
To fix it they say you have to add the url path to a .env file when deploying. I’m working on localhost so this shouldn't be a problem, but after adding it, still the same error.
When I comment out the async session callback in [...nextauth] file, the error doesn’t pop up and the session is “authenticated” but doesn’t persist. I’ve been staring it at this for a good while now and could use some help!
[...nextauth].js
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { PrismaClient } from "#prisma/client";
const prisma = new PrismaClient();
export default NextAuth({
providers: [
CredentialsProvider({
async authorize(credentials, res) {
//find existing user
const user = await prisma.user.findUnique({
where: {
email: credentials.email,
},
});
if (
credentials.email === user.email &&
credentials.password === user.password
) {
return user;
} else {
return res.status(409).send({ message: "No user with that email." });
}
},
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
return token;
}
},
//commenting this function and no error shows up
async session({ session, token }) {
if (token) {
session.id = token.id;
return session;
}
},
},
secret: "mysecret",
jwt: {
secret: "mysecret",
encryption: true,
},
session: {
strategy: "jwt",
maxAge: 1 * 24 * 60 * 60,
},
});
auth-form
import { signIn, useSession } from "next-auth/react";
export default function AuthForm() {
const { data: session } = useSession();
const handleSubmit = async (userData) => {
const { error, ok, url } = await signIn("credentials", {
redirect: false,
email: userData.email,
password: userData.password,
callbackUrl: "/profiel",
});
if (ok) {
router.push(url);
} else {
alert(error);
}
};
return (
<Form onSubmit={handleSubmit}>
<Field id="email" />
<Field id="password" />
<button type="submit">{isRegistered ? "Sign in" : "Register"}</button>
</Form>
);
}
_app.js
import { SessionProvider } from "next-auth/react";
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
}
The session and jwt callbacks need to return a session and jwt object respectively. You need to move the return statements in each function after the if block.
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
async session({ session, token }) {
if (token) {
session.id = token.id;
}
return session;
}
}
I am using Next-Auth Credentials provider to authenticate using our existing API.
When I follow the directions on https://next-auth.js.org/configuration/callbacks
like this:
callbacks: {
async jwt({ token, user }) {
if (user) {
token.accessToken = user.jwt
}
return token
},
async session({ session, token, user }) {
session.accessToken = token.accessToken
return session
}
}
the resulting session object from useSession() looks like this:
{
expires: "2022-03-22T18:29:02.799Z",
user: {email: 'john#nextIsGreat.com'}
}
I can't use that as it does not have the token available.
So I was able to make up my own working solution, but it is kind of strange because of the way things are grouped together. Here is what I am doing now, that I am trying to figure out how to do better. I use comments to point out the problem areas:
[...nextauth].js:
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import axios from 'axios'
export default NextAuth({
providers: [
Credentials({
name: 'Email and Password',
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' }
},
authorize: async (credentials) => {
const url = process.env.API_URL + '/authenticate'
const result = await axios.post(url, {
username: credentials.username,
password: credentials.password
})
const user = result.data
console.log(user)
//It logs this:
/*
{
jwt: 'eyJhbasU1OTJ9.NQ356H4Odya62KmN...', //<---***This is the token i pass in to all of my API calls****
user: {
userId: 207,
email: 'john#nextIsGreat.com',
firstName: 'John',
lastName: 'Doe',
roleId: 1,
}
}
*/
if (user) {
return Promise.resolve(user)
} else {
return Promise.resolve(null)
}
}
})
],
callbacks: {
async jwt({ token, user }) {
if (user) {
if (user.jwt) {
token = { accessToken: user.jwt, restOfUser: user.user }
}
}
return token
},
async session(seshProps) {
return seshProps
}
}
})
Home.js:
export const Home = () => {
const { data: session } = useSession()
console.log(session)
//LOGS THIS --->
/*
{
"session": { "user":{}, "expires":"2022-03-22T17:06:26.937Z"},
"token":{
"accessToken":"eyJ...",
"iat":1645376785,
"exp":1647968785,
"jti":"41636a35-7b9a-42fd-8ded-d3dfgh123455a"
"restOfUser": {
"userId":207,
"email":"john#nextIsGreat.com",
"firstName":"John",
"lastName":"Doe",
"roleId":1
}
}
{
*/
const getPosts=()=> {
const url = 'localhost:4000/posts'
const {data} = axios.get(url, {
Authorization: session.token.accessToken <--**This is the way I am calling my API
})
console.log(data)
}
return (
<div onClick={getPosts}>
Hello, {session.token.restOfUser.firstName}
/* I have to access it like this now, which seems wrong ***** */
</div>
)
}
Cheers for creating your own solution but you do not need it. NextAuth CredentialsProvider handles it already by setting your NextAuth session configuration to session: {strategy: "jwt", ... }.
You can also remove your callbacks for jwt() and session() and remove your owned generated JWT access token. As you do not need it, this way you can authenticate your existing system.
And at your CredentialsProvider({authorize(){}} authorize method. If you had directly connected to the user database, you can directly look up the user credential without doing a post request since it is already considered a server-side function.
I am trying to check if a user is authenticated and redirect them depending on the page they are in. for example if the user is logged in and they try to visit the login or signup page, they should be redirected. I have a middleware for that.
when I log in the user, the authenticateUser action runs and the user is created, when I check my cookies and local storage on the browser, I see that it is set correctly, but when I visit the login page after logging in, it doesn't redirect me.
middleware/altauth.js
export default function (context) {
console.log(context.store.getters('profile/isAuthenticated'))
if (context.store.getters.isAuthenticated) {
context.redirect('/')
}
}
also the token is both saved using Cookies and local storage and is persistence is through this middleware
middleware/checkauth.js
export default function (context) {
if(context.hasOwnProperty('ssrContext')) {
context.store.dispatch('profile/initAuth', context.ssrContext.req);
} else {
context.store.dispatch('profile/initAuth', null);
}
}
and below are the values for my store
import Cookie from 'js-cookie';
export const state = () => ({
token: null,
})
export const mutations = {
setToken(state, token) {
state.token = token
},
clearToken(state) {
state.token = null
}
}
export const actions = {
async authenticateUser(vuexContext, authData) {
let authUrl = 'https://look.herokuapp.com/signup/'
if (authData.isLogin) {
authUrl = 'https://look.herokuapp.com/login/'
}
return this.$axios
.$post(authUrl, authData.form)
.then(data => {
console.log(data);
const token = data.token
vuexContext.commit('setToken', token)
localStorage.setItem("token", token)
Cookie.set('jwt', token);
})
.catch(e => console.log(e))
},
initAuth(vuexContext, req) {
let token
if (req) {
if (!req.headers.cookie) {
return;
}
const jwtCookie = req.headers.cookie
.split(';')
.find(c => c.trim().startsWith('jwt='));
if (!jwtCookie) {
return;
}
token = jwtCookie.split('=')[1];
} else {
token = localStorage.getItem('token');
if (!token) {
return;
}
}
vuexContext.commit('setToken', token);
}
}
export const getters = {
isAuthenticated(state) {
return state.token != null;
},
}
please help, i don't know what the problem can be
Here is a basic but full example for auth system in SSR nuxt
You will need two apis for this, one will return token info with user info, and the other will return user info only.
for example
POST http://example.com/api/auth/authorizations
{
token: 'abcdefghijklmn',
expired_at: 12345678,
user: {
name: 'Tom',
is_admin: true
}
}
// this need authed
GET http://example.com/api/auth/user
{
name: 'Tom',
is_admin: true
}
nuxt.config.js
plugins:[
'~plugins/axios',
],
buildModules: [
'#nuxtjs/axios',
],
router: {
middleware: [
'check-auth'
]
},
./pages/login.vue
<template>
<form #submit.prevent="login">
<input type="text" name="username" v-model="form.username">
<input type="password" name="password" v-model="form.password">
</form>
</template>
<script type="text/javascript">
export default{
data(){
return {
form: {username: '', password: ''}
}
},
methods: {
login(){
this.$axios.post(`/auth/authorizations`, this.form)
.then(({ data }) => {
let { user, token } = data;
this.$store.commit('auth/setToken', token);
this.$store.commit('auth/updateUser', user);
this.$router.push('/');
})
}
}
}
</script>
store/index.js
const cookieFromRequest = (request, key) => {
if (!request.headers.cookie) {
return;
}
const cookie = request.headers.cookie.split(';').find(
c => c.trim().startsWith(`${key}=`)
);
if (cookie) {
return cookie.split('=')[1];
}
}
export const actions = {
nuxtServerInit({ commit, dispatch, route }, { req }){
const token = cookieFromRequest(req, 'token');
if (!!token) {
commit('auth/setToken', token);
}
}
};
middleware/check-auth.js
export default async ({ $axios, store }) => {
const token = store.getters['auth/token'];
if (process.server) {
if (token) {
$axios.defaults.headers.common.Authorization = `Bearer ${token}`;
} else {
delete $axios.defaults.headers.common.Authorization;
}
}
if (!store.getters['auth/check'] && token) {
await store.dispatch('auth/fetchUser');
}
}
store/auth.js
import Cookie from 'js-cookie';
export const state = () => ({
user: null,
token: null
});
export const getters = {
user: state => state.user,
token: state => state.token,
check: state => state.user !== null
};
export const mutations = {
setToken(state, token){
state.token = token;
},
fetchUserSuccess(state, user){
state.user = user;
},
fetchUserFailure(state){
state.user = null;
},
logout(state){
state.token = null;
state.user = null;
},
updateUser(state, { user }){
state.user = user;
}
}
export const actions = {
saveToken({ commit }, { token, remember }){
commit('setToken', token);
Cookie.set('token', token);
},
async fetchUser({ commit }){
try{
const { data } = await this.$axios.get('/auth/user');
commit('fetchUserSuccess', data);
}catch(e){
Cookie.remove('token');
commit('fetchUserFailure');
}
},
updateUser({ commit }, payload){
commit('updateUser', payload);
},
async logout({ commit }){
try{
await this.$axios.delete('/auth/authorizations');
}catch(e){}
Cookie.remove('token');
commit('logout');
}
}
plugins/axios.js
export default ({ $axios, store }) => {
$axios.setBaseURL('http://example.com/api');
const token = store.getters['auth/token'];
if (token) {
$axios.setToken(token, 'Bearer')
}
$axios.onResponseError(error => {
const { status } = error.response || {};
if (status === 401 && store.getters['auth/check']) {
store.commit('auth/logout');
}
else{
return Promise.reject(error);
}
});
}
Then you can do what you want in your middleware, such as check auth
middleware/auth.js
export default function ({ store, redirect }){
if (!store.getters['auth/check']) {
return redirect(`/login`);
}
}