I am using Django and Next.js (Version 13 with the app dir enabled). Now I have two questions:
What is the best practice to deal with the access token I receive after I do the authorize call to the django backend? Is it correct how I put it into the callbacks?
export const authOptions = {
secret: process.env.NEXTAUTH_SECRET,
providers: [
CredentialsProvider({
name: 'Django',
credentials: {
username: { label: "Username", type: "text", placeholder: "mail#domain.com" },
password: { label: "Password", type: "password" }
},
async authorize(credentials, req) {
// Do access call
const resToken = await fetch(process.env.AUTH_ENDPOINT, {
method: 'POST',
body: JSON.stringify(credentials),
headers: { "Content-Type": "application/json" }
})
const jwt_token = await resToken.json()
// fetching user data
const resUser = await fetch(`${process.env.BACKEND_URL}/auth/users/me/`, {
method: 'GET',
headers: { "Content-Type": "application/json",
"Authorization": `JWT ${jwt_token.access}` }
})
const user = await resUser.json()
if (resUser.ok && jwt_token.access) {
user.access_token = jwt_token.access
user.refresh_token = jwt_token.refresh
return user
}
// Return null if user data could not be retrieved
return null
}
})
],
session: {
strategy: "jwt",
},
jwt: { encryption: true, },
callbacks: {
async jwt({ token, user }) {
if (user) {
token.access_token = user.access_token
token.refresh_token = user.refresh_token
console.log("if executed")
}
return token
},
async session({ session, token, user }) {
if (!session) {
session.access_token = user.access_token
session.refresh_token = user.refresh_token
session.user = user
}return session;
},
}
}
export default NextAuth(authOptions)
I have the provider wrapped in the provider.js file as shown below. Now I was wondering if I need to passt the session as <SessionProvider session={session}> in the code below? And if yes - could you tell me how?
'use client'
import { SessionProvider } from 'next-auth/react'
export function Providers({ children }) {
return (
<SessionProvider>
{children}
</SessionProvider>
);
}
Thank you!
Related
I am trying to have a role for the user in the session
This is what I get from session.user on the client :
{ "email": "test value" }
what I want to get :
{
"email": "test value",
"role": "user"
}
For some reason I can access the role on the server side but not on the client
[...nextauth].ts :
//..
const authOptions: NextAuthOptions = {
session: {
strategy: "jwt",
},
providers: [
CredentialsProvider({
type: "credentials",
credentials: {},
async authorize(credentials, req) {
const { email, password } = credentials as {
email: string;
password: string;
};
const saltRounds = 10;
const db = path.join(process.cwd(), "db");
const users = JSON.parse(fs.readFileSync(db + "/users.json", "utf-8"));
type User = {
id: string;
email: string;
name: string;
role: "user" | "admin";
password: string;
};
for (let i = 0; i < users.length; i++) {
const e = users[i] as User;
const emailMatch = e.email === email;
if (emailMatch) {
const passwordMatch = bcrypt.compareSync(password, e.password);
if (passwordMatch) {
console.log("user loggedin", e);
return {
id: e.id,
email: e.email,
name: e.name,
role: e.role,
};
}
}
}
throw new Error("Invalid email or password");
},
}),
],
pages: {
signIn: "/auth/signin",
},
callbacks: {
jwt(params) {
if (params.user?.role) {
params.token.role = params.user.role;
}
console.log("jwt", params);
return params.token;
},
},
};
export default NextAuth(authOptions);
I have tried searching for how to do it and I dont see what's wrong with my code.
Here you are not setting the session you have to use the session callback to update the session from the returned token:
async jwt(params) {
if (params.user?.role) {
params.token.role = params.user.role;
}
if (params.user?.email) {
params.token.email = params.user.email;
}
return params.token;
},
async session({ session, token }) {
session.role = token.role;
session.email = token.email;
return session;
},
For some reason I can access the role on the server side but not on the client
that's the role from the token because you have added the property role to it now you have to add properties to your session
I deployed a Nextjs(v13) app with AWS Amplify and using NextAuth(v4.17.0). I'm using CredentialsProvider with a custom server. All works great in development environment, but in production the session callback doesn't fire and the session is empty, even if the token gets created in the database
/page/api/auth/[...nextauth].tsx disregard the console logs lol
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import jwt_decode from "jwt-decode";
import { TokenInfo } from "../../../components/types/auth_types";
async function refreshAccessToken(token) {
try {
console.log("BUT WHY?");
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/token/refresh/`,
{
method: "POST",
body: JSON.stringify({refresh: token.refreshToken}),
headers: {"Content-Type": "application/json"},
}
);
if (!res.ok) throw "refreshError";
const responseJson = await res.json();
return {
...token,
accessToken: responseJson.access,
}
} catch(error) {
return {
...token,
error: "RefreshAccessTokenError",
}
}
}
export const authOptions = {
providers: [
CredentialsProvider({
id: "credentials",
name: "Credentials",
credentials: {
email: { label: "Username", type: "text", placeholder: "" },
password: { label: "Password", type: "password" }
},
async authorize(credentials, req) {
const userCredentials = {
email: credentials.email, password: credentials.password
};
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/token/`,
{
method: "POST",
body: JSON.stringify(userCredentials),
headers: {"Content-Type": "application/json"},
credentials: "include",
}
);
console.log("res", res);
if (res.ok) {
const responseJson = await res.json();
console.log("resJson", responseJson);
const tokenInfo: TokenInfo = jwt_decode(responseJson.access);
console.log("tokenInfo", tokenInfo);
return {
id: tokenInfo.user_id.toString(),
email: tokenInfo.email,
firstName: tokenInfo.first_name,
lastName: tokenInfo.last_name,
isStaff: tokenInfo.is_staff,
accessToken: responseJson.access,
refreshToken: responseJson.refresh,
};
}
return null;
} catch(e) {
return null;
}
}
})
],
callbacks: {
async jwt({ token, account, user }) {
if (account && user) {
console.log("got into token", user);
token.firstName = user.firstName;
token.lastName = user.lastName;
token.refreshToken = user.refreshToken;
token.accessToken = user.accessToken;
}
if (token.accessToken) {
console.log("got in this if instead")
const decodedToken: TokenInfo = jwt_decode(token.accessToken);
if (Date.now() < decodedToken.exp * 1000) {
console.log("got here, returned properly");
return token;
}
}
console.log("got here, not properly, why?");
return await refreshAccessToken(token);
},
async session({ session, token }) {
console.log("getting session");
session.user.firstName = token.firstName;
session.user.lastName = token.lastName;
session.accessToken = token.accessToken;
console.log("sess", session);
return session;
}
},
secret: process.env.NEXT_AUTH_SECRET,
session: {
maxAge: 2 * 24 * 60 * 60, // two days
}
};
export default NextAuth(authOptions);
I have searched as best as I could and couldn't find anything that I didn't do already.
My understanding is I don't need to set session: { strategy: "jwt"} since that's the default.
I have NEXT_AUTH_SECRET="mysecret", NEXT_PUBLIC_API_URL="https://www.backend_domain.com" and NEXTAUTH_URL="https://www.frontend_domain.com" set properly in .env.production and the API calls succeed, as well as the NextAuth calls return 200 status code, no errors in Amplify logs
Edit 1:
If I navigate to /api/auth/signin/credentials and use the default UI for login, the session gets created successfully
I'm using an IDP that requires a nonce
I have my nextauth like this (note that i passed my nonce in the authorization step) :
import NextAuth, { NextAuthOptions } from 'next-auth'
const randomString = (length: number) => {
let text = ''
let possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length))
}
return text
}
const nonce = `nonce${randomString(32)}`
const authOptions: NextAuthOptions = {
providers: [
{
issuer: 'https://fcp.integ01.dev-franceconnect.fr',
id: 'franceconnect',
clientSecret: process.env.FRANCE_CONNECT_SECRET || 'undefined',
clientId: process.env.FRANCE_CONNECT_ID || 'undefined',
name: 'FranceConnect',
type: 'oauth',
idToken: true,
client: {
authorization_signed_response_alg: 'HS256',
id_token_signed_response_alg: 'HS256'
},
authorization: {
url: 'https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize',
params: {
scope: 'openid given_name gender',
nonce,
redirect_uri: `http://localhost:3000/api/auth/callback/franceconnect`,
},
},
token:`https://fcp.integ01.dev-franceconnect.fr/api/v1/token`,
userinfo:
'https://fcp.integ01.dev-franceconnect.fr/api/v1/userinfo',
profile(profile) {
console.log(profile)
return profile
},
},
],
debug: true,
secret: 'hdh-secret',
callbacks: {
async jwt({ token, account }) {
return token
},
async session({ session, token, user }) {
return session
},
},
}
export default NextAuth(authOptions)
I'm having this error :
[next-auth][error][CALLBACK_OAUTH_ERROR]
https://next-auth.js.org/errors#callback_oauth_error nonce mismatch, expected undefined, got: nonceZDBoVu2bD1rRESxh7y4kgZ76A6NiP22e RPError: nonce mismatch, expected undefined, got: nonceZDBoVu2bD1rRESxh7y4kgZ76A6NiP22e
at Client.validateIdToken (C:\Users\Shadow\Documents\Projets\HDH\front\node_modules\openid-client\lib\client.js:784:13)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Client.callback (C:\Users\Shadow\Documents\Projets\HDH\front\node_modules\openid-client\lib\client.js:487:7)
at async oAuthCallback (C:\Users\Shadow\Documents\Projets\HDH\front\node_modules\next-auth\core\lib\oauth\callback.js:114:16)
at async Object.callback (C:\Users\Shadow\Documents\Projets\HDH\front\node_modules\next-auth\core\routes\callback.js:50:11)
at async NextAuthHandler (C:\Users\Shadow\Documents\Projets\HDH\front\node_modules\next-auth\core\index.js:186:28)
at async NextAuthNextHandler (C:\Users\Shadow\Documents\Projets\HDH\front\node_modules\next-auth\next\index.js:23:19)
at async C:\Users\Shadow\Documents\Projets\HDH\front\node_modules\next-auth\next\index.js:59:32
at async Object.apiResolver (C:\Users\Shadow\Documents\Projets\HDH\front\node_modules\next\dist\server\api-utils\node.js:179:9)
at async DevServer.runApi (C:\Users\Shadow\Documents\Projets\HDH\front\node_modules\next\dist\server\next-server.js:381:9) {
name: 'OAuthCallbackError',
code: undefined
}
If I remove the nonce I got this error from the IDP : {"status":"fail","message":"The following fields are missing or empty : nonce"}
How am I supposed to tell next auth to use a nonce ?
I manage to make it works by doing myself the token and userinfo requests (thanks to request method).
Here is the final code :
providers: [
{
issuer: 'https://fcp.integ01.dev-franceconnect.fr',
id: 'franceconnect',
clientSecret: process.env.FRANCE_CONNECT_SECRET || 'undefined',
clientId: process.env.FRANCE_CONNECT_ID || 'undefined',
name: 'FranceConnect',
type: 'oauth',
authorization: {
url: 'https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize',
params: {
scope: 'openid profile email',
nonce,
redirect_uri: `${process.env.NEXTAUTH_URL}/api/auth/callback/franceconnect`,
},
},
token: {
async request(context) {
const body = {
grant_type: 'authorization_code',
redirect_uri: `${process.env.NEXTAUTH_URL}/api/auth/callback/franceconnect`,
client_id: process.env.FRANCE_CONNECT_ID || 'undefined',
client_secret:
process.env.FRANCE_CONNECT_SECRET || 'undefined',
code: context.params.code || 'undefined',
}
const data = new URLSearchParams(body).toString()
try {
const r = await axios({
method: 'POST',
headers: {
'content-type':
'application/x-www-form-urlencoded',
},
data,
url: `https://fcp.integ01.dev-franceconnect.fr/api/v1/token`,
})
return { tokens: r.data }
} catch (err: any) {
console.error(err)
throw new Error(err)
}
},
},
userinfo: {
url: 'https://fcp.integ01.dev-franceconnect.fr/api/v1/userinfo',
params: { schema: 'openid' },
async request(context) {
const r = await axios({
method: 'GET',
url: 'https://fcp.integ01.dev-franceconnect.fr/api/v1/userinfo?schema=openid',
headers: {
Authorization: `Bearer ${context.tokens.access_token}`,
},
})
return r.data
},
},
profile(profile) {
return {
...profile,
name: `${profile.given_name} ${profile.family_name}`,
id: profile.email,
}
},
},
],
I am getting this error.
https://next-auth.js.org/errors#oauth_callback_error expected 200 OK, got: 400 Bad Request
Please help me solve it.
THis is my next auth config
const authHandler: NextApiHandler = (req, res) => NextAuth(req, res, options)
export default authHandler
const options: NextAuthOptions = {
providers: [
FacebookProvider({
clientId: process.env.FACEBOOK_CLIENT_ID,
clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
}),
],
callbacks: {
async session({ session, token }) {
if (session?.user) {
session.user.id = token.id
session.user.createdAt = token.createdAt
session.user.role = token.role
}
return session
},
async jwt({ user, token }) {
if (user) {
token.id = user.id
// #ts-ignore
token.createdAt = user.createdAt
// #ts-ignore
token.role = user.role
}
return token
},
},
session: {
strategy: 'jwt',
},
adapter: PrismaAdapter(prisma),
secret: process.env.NEXTAUTH_SECRET,
}
]1
Using NextAuth for GraphQL authentication with Apollo client in Next.js encounter the error
Hooks can only be called inside of the body of a function.
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import { useMutation, useApolloClient } from '#apollo/client';
import { LOGIN_MUTATION } from '../../../graphql/mutations';
import { getErrorMessage } from '../../../lib';
export default (req, res) =>
NextAuth(req, res, {
providers: [
Providers.Credentials({
name: 'Credentials',
credentials: {
identifier: { label: "Email", type: "text" },
password: { label: "Password", type: "password" }
},
authorize: async (credentials) => {
const client = useApolloClient();
const [errorMsg, setErrorMsg] = useState();
const [login] = useMutation(LOGIN_MUTATION);
try {
await client.resetStore();
const { data: { login: { user, jwt } } } = await login({
variables: {
identifier: credentials.identifier,
password: credentials.password
}
});
if (user) {
return user;
}
} catch (error) {
setErrorMsg(getErrorMessage(error));
}
}
})
],
site: process.env.NEXTAUTH_URL || "http://localhost:3000",
session: {
jwt: true,
maxAge: 1 * 3 * 60 * 60,
updateAge: 24 * 60 * 60,
},
callbacks: {},
pages: {
signIn: '/auth/signin'
},
debug: process.env.NODE_ENV === "development",
secret: process.env.NEXT_PUBLIC_AUTH_SECRET,
jwt: {
secret: process.env.NEXT_PUBLIC_JWT_SECRET,
}
});
I am wondering is there anyway to make this work with apollo?
Thank you for the helps.
As in the comments rightfully pointed out, you can't use hooks in server-side code. You would have to create a new ApolloClient like this:
const client = new ApolloClient()
Then you can do queries like this for example:
const { data } = await client.query({
query: "Your query",
variables: { someVariable: true }
});
Best would be the to move the creation of the client to a separate external file as a function and import it in your server-side code whenever needed. Like done here for example.
Edit:
As #rob-art correctly remarks in the comments, for a [mutation][2], the code should look more like this:
const { data } = await client.mutate({
mutation: "Your query",
variables: { someVariable: true }
});