I have a Next application that let's the user signup using their discord account. The signup part is working. Now I am wondering how can I let the user sign-in using the discord account without going through the authorize part again, if they had already done the signup.
In the /pages/api/auth/[...nextauth].ts file
export default (req: NextApiRequest, res: NextApiResponse<any>) => {
if (req.query.firstName && req.query.lastName) {
firstName = req.query.firstName;
lastName = req.query.lastName;
}
return NextAuth(req, res, {
providers: [
DiscordProvider({
clientId: process.env.DISCORD_CLIENT_ID,
clientSecret: process.env.DISCORD_CLIENT_SECRET,
authorization: { params: { scope: DISCORD_SCOPES } },
}),
],
session: { strategy: "jwt" },
callbacks: {
async session({ session, token, user }) {
session.jwt = token.jwt;
session.id = token.id;
return session;
},
async jwt({ token, user, account }) {
}
}
});
}
Using above logic I am saving the user data to strapi after signup.
How to let the user sign-in with discord without getting a new access token and going through authorization
If the user already authorized your app to log in. You can use the following in your front-end client.
import { signIn } from "next-auth/react";
signIn("discord", { callbackUrl: '', redirect: true }, { prompt: "none" });
References:
https://next-auth.js.org/getting-started/client#additional-parameters
Related
I am using the Google Provider to allow users to login to my website. My code in /api/auth/[...nextauth].js like this:
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
const fetchUserInfo = (email) => {
// Get user info in database by google email
}
export default NextAuth({
providers: [
Providers.Google({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
callbacks: {
async signIn(user, account, profile) {
if (account.provider === 'google' &&
profile.verified_email === true &&
profile.email.endsWith('#example.com')) {
return true
} else {
return false
}
},
},
pages: {
signIn: '/auth/signin',
},
secret: process.env.SECRET
})
I want to execute function fetchUserInfo after user has been authenticated. fetchUserInfo get the user's information from database, and i want to save it to the session so i can use it later. The default session i have is the information returned by google are: email, name, image.
I have the GoogleProvider working, and returning a valid session. But when I create a CredentialsProvider it isn't working.
The credentials provider successfully creates a user in the database with graphql. And I'm able to log the returned user at the bottom.
import { client } from '#/src/graphql/apollo-client'
import UserOperations from '#/src/graphql/operations/user'
import { CreateUserData, CreateUserInput } from '#/src/util/types'
import credentialsProvider from 'next-auth/providers/credentials'
export default CredentialsProvider({
name: '',
credentials: {
email: { label: 'Username', type: 'text', placeholder: 'email' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials, req) {
const signUp = UserOperations.Mutations.signUp
const response = await client.mutate<CreateUserData, CreateUserInput>({
mutation: signUp,
variables: {
email: credentials?.email!,
password: credentials?.password!
}
})
const { data } = response
const user = data
console.log('the user data returned is ', user)
if (user) {
return user
}
return null
}
})
I'm using that provider along side a GoogleProvider in [...nextauth].ts
import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
import { PrismaAdapter } from '#next-auth/prisma-adapter'
import { PrismaClient } from '#prisma/client'
import CredentialsProvider from './providers/credentials'
const prisma = new PrismaClient()
export default NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
credentialsProvider, //the credentials provider from above
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string
})
],
secret: process.env.JWT_SECRET,
callbacks: {
async session({ session, token, user }) {
//here the google provider returns values, but the credentials provider does not.
console.log('session ', session, ' user ', user)
return {
...session,
user: { ...session.user, ...user } // combine the session and db user
}
}
}
})
Inside of async session you can see I have another console log. When using the google provider I can log the session and user, but I'm getting session:null and user:null with the credentials provider.
So I'm not sure where session gets set with Credentials Provider or what other steps I may be missing.
Happy to provide any other files if they help, but I thought this would be simplest to start.
Help is much appreciated, thanks.
EDIT -
It looks like someone was able to use credentials provider along side Prisma adapter here. I tried adding the encode/ decode token block that they used, but still no luck.
I may be confused about how JWT fits into the picture.
I was able to get this to work!! I just use strategy 'jwt', and then in the jwt callback I had access to the user and token. So I could assign the user to user.token and then the token is available in the session callback.
So with other providers like google I could access user and session directly in the session callback. But with credentials I had to go through the token parameter.
import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
import { PrismaAdapter } from '#next-auth/prisma-adapter'
import { PrismaClient } from '#prisma/client'
import credentialsProvider from '../../../providers/credentials'
const prisma = new PrismaClient()
export default NextAuth({
adapter: PrismaAdapter(prisma),
session: {
strategy: 'jwt'
},
providers: [
credentialsProvider,
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string
})
],
secret: process.env.JWT_SECRET,
callbacks: {
jwt: async ({ token, user }) => {
user && (token.user = user)
return token
},
//whatever value we return here will be the value of the next-auth session
async session({ session, token, user }) {
return {
...session,
user: { ...session.user, ...user, ...token.user! } // combine the session and db user
}
}
}
})
I'm struggling with next-auth for authorization.
In our project, two domains. so the main domain has a login page, after login set accessToken in redux and refresh Token in a cookie. But subDomain does not have a login page. Because main domain makes user redirect subDomain with refresh token(http only cookie). In a subDomain call API includes a refresh Token, and then the server gives a refresh token and access token. accessToken set headers. when accessToken is expired, call Api includes a refresh token again
this is our authorization process.
But I can not find the way by using next-auth in the subDomain. Because the page does not have a login. I want to make an auto-login with a refresh token. How to set up the [...nextauth].ts and other file settingss
Thank you!
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import axios from "axios";
axios.defaults.baseURL = `${process.env.NEXT_PUBLIC_API_URL}`;
export default NextAuth({
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {},
async authorize(credentials, req) {
const response = await axios.post("/api/auth/getRefreshToken");
return response.data;
},
}),
],
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
return true;
},
async redirect({ url, baseUrl }) {
return baseUrl;
},
async session({ session, token, user }) {
session.accessToken = token.accessToken;
return session;
},
async jwt({ token, account }) {
// Persist the OAuth access_token to the token right after signin
if (account) {
token.accessToken = account.access_token;
}
return token;
},
},
// pages: {
// signIn: "/login",
// },
});
We are doing OTP auth in our website. So in order to get authorized, a visitor enter his phone number in input and we send him a OPT number and he again enters the sent opt, then if it matches, we send him his account credendials (token, userID) if exists or we create new and we want to save that credentails in session useing next-auth.
This is where i got so far:
export default NextAuth({
providers: [
CredentialsProvider({
credentials: {
phoneNumber: { label: 'PhoneNumber', type: 'text' },
code: { label: 'Code', type: 'text' },
type: { label: 'Type', type: 'text' },
},
async authorize(credentials, req) {
const user_needs = await requests.auth.signInEnterOtp(
credentials.phoneNumber,
credentials.code,
credentials.type
)
return user_needs.ok ? true : null
},
}),
],
callbacks: {
async session({ session, user, token }) {
return session
},
},
secret: process.env.JWT_SECRET,
})
I need to save the user_needs in session but how can i pass it throuh authorize to session?
I tried returning the user_need in authorize but it was not passed to session callback.
Eventually i figured it out like this:
async authorize(credentials, req) {
const res = fetchUserInfo(credentials.opt)
if(res.ok) return {user: res.data} // res.data contains whatever received from DB call => fetchUserInfo(credentials.opt)
return null
},
callbacks: {
async jwt({ token, user }) {
// the user present here gets the same data as received from DB call made above -> fetchUserInfo(credentials.opt)
return { ...token, ...user }
},
async session({ session, user, token }) {
// user param present in the session(function) does not recive all the data from DB call -> fetchUserInfo(credentials.opt)
return token
},
},
Edit: 2023 Feb 15
I myself understood callback cycle better now:
authorize --> jwt --> session
jwt callback(cb) accepts the user object that authorize cb returns.
By default jwt retuns the token and things return from there is then available in the token object of session cb
Example:
async authorize(credentials, req) {
return { user: { role: 'ADMIN' } }
},
async jwt({ token, user }) {
return { ...token, role: user.role }
},
async session({ session, token }) {
return { ...session, token.role }
}
But when i use Providers, i won't have authorize cb, so to get user's role i need to query db in jwt cb but this callback runs a lot and i don't know what is the better option
A.Anvarbekov, i think it works but maybe in callbacks we should also pass other session properties? something like:
async session({ session, token }) {
return {
...session,
user:{...token.user}
},
I try to get credentials auth with next-auth, but I have no experience to use it and whatever I do, i get following message:
[next-auth][error][callback_credentials_jwt_error] Signin in with credentials is only supported if JSON Web Tokens are enabled
https://next-auth.js.org/errors#callback_credentials_jwt_error
This is my src/pages/api/auth/[...nextauth].js file.
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
import User from "#models/User"
const options = {
NEXTAUTH_URL:process.env.NEXTAUTH_URL,
providers: [
Providers.Credentials({
// The name to display on the sign in form (e.g. 'Sign in with...')
name: 'Avista',
// The credentials is used to generate a suitable form on the sign in page.
// You can specify whatever fields you are expecting to be submitted.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
email: {label: "Email", type: "text"},
password: {label: "Password", type: "password"}
},
authorize: async (credentials) => {
// Add logic here to look up the user from the credentials supplied
// const user = {id: 1, name: 'J Smith', email: 'jsmith#example.com'}
const user = await User.findOne()
console.log("Данные входа", credentials)
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
}
}
}),
Providers.Email({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD
}
},
from: process.env.EMAIL_FROM
}),
],
// A database is optional, but required to persist accounts in a database
database: process.env.MONGO_URI,
secret: process.env.JWT_SIGNING_PRIVATE_KEY,
},
}
I don't know what I do wrong.
try adding this to your [...nextauth].js page
session: {
jwt: true,
// Seconds - How long until an idle session expires and is no longer valid.
maxAge: 30 * 24 * 60 * 60, // 30 days
},
read more about the options here: https://next-auth.js.org/configuration/options#jwt
This might be useful for someone else.
You need to specify that you want to use the jwt auth style.
Make options.session to be:
{ jwt: true }
If you using next-auth version 4, you should instead do:
{ strategy: 'jwt'}
Add this after providers: []
callbacks: {
jwt: async ({ token, user }) => {
user && (token.user = user)
return token;
},
session: async ({ session, token }) => {
session.user = token.user
return session;
}
},
secret: "secret",
jwt: {
secret: "ksdkfsldferSDFSDFSDf",
encryption: true,
},