refresh access token with apollo client in Next.js - next.js

I'm working with next.js and apollo client to get an access token with a refresh token from the server and I searched the web and find apollo-link-token-refresh that does something like silent refresh, so I follow along with example and was happy that after 2 days I finish my project Authentication but no it didn't work. is there any problem in my code?
const refreshLink = new TokenRefreshLink({
accessTokenField: "token",
isTokenValidOrUndefined: () => {
if (!cookie.get("JWT")) {
return true;
}
if (token && jwt.decode(token)?.exp * 1000 > Date.now()) {
return true;
}
},
fetchAccessToken: async () => {
if (!cookie.get("JWT")) {
return true;
}
const response = await fetch(`${NEXT_PUBLIC_SERVER_API_URL}`, {
method: "POST",
headers: {
authorization: token ? "JWT " + token : "",
"content-type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
refreshToken(refreshToken: "${cookie.get("JWTRefreshToken")}") {
token
payload
refreshToken
refreshExpiresIn
}
}
`,
}),
});
return response.json();
},
handleFetch: (newToken) => {
cookie.remove("JWT", { path: "" });
cookie.set("JWT", newToken, {
expires: data.tokenAuth.payload.exp,
secure: process.env.NODE_ENV !== "production",
path: "",
});
},
handleResponse: (operation, accessTokenField) => (response) => {
if (!response) return { newToken: null };
return { newToken: response.data?.refreshUserToken?.token };
},
handleError: (error) => {
console.error("Cannot refresh access token:", error);
},
});
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: authLink.concat(refreshLink).concat(
new HttpLink({
uri: process.env.NEXT_PUBLIC_SERVER_API_URL,
credentials: "same-origin",
}),
),
cache
})
)

Related

Next-Auth who to deal with backend access token

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!

Retrieve data from the firebase dynamic link

Im trying to get the data from the generated firebase dynamic link.
I passed Id while generating the dynamic link. I need to get the particular id from the generated dynamic link. Can you please help me.
Where it generates link as :https://thejyotistore.page.link/jNngseAasXzE5SuSA
const ShareLink = ({
fallbackUrl, id, product }) =>
{
const buildLink = async () =>
{
let link = await axios({
method: "POST",
url:
`https://firebasedynamiclinks
.googleapis.com/v1/shortLinks?
key=AIzaSyDNZvtkjAqf8c9esg
gSEzV2 L7. 3vEUv1FfQ`,
headers: {
"Content-Type":
"application/json",
},
data: {
dynamicLinkInfo: {
domainUriPrefix: `https://thejyotistore.page.link`,
link: `https://youtube.com/${id}`,
androidInfo: {
androidPackageName: "com.jyotistore.main",
},
},
},
});
if (link.status === 200 && link.data.hasOwnProperty("shortLink")) {
console.log(link.data.shortLink);
return link.data.shortLink;
}
};
const shareLink = async () =>
{
let shareUrl;
try {
shareUrl = await buildLink();
console.log(shareUrl);
} catch (error) {
console.log(error);
}
try {
if (shareUrl !== "") {
const result = await Share.share({
message: `Hey, ${"\n \n"}I would like to invite you to check out this New App from Jyoti Store. ${"\n \n"} ${product}Download the App now: ${"\n \n"} ${shareUrl}`,
});
}
} catch (error) {
console.log(error);
}
};

NextAuth session callback does not trigger

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

NextJS contact form with nodemailer throws Error 500 in Production

I have a simple contact form in a NextJS app and I wanted to switch from emailjs to something custom.
The contact form works perfect in development, but in production(cpanel, not vercel) I get a status 500 error(Internal Server Error). I've researched around and implemented async/await, promises, res.send status and everything I've found but I still can't find a way through.
Here's the post method:
const checkForm = async () => {
try {
const values = await form.validateFields();
fetch('/api/contact', {
method: 'POST',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: JSON.stringify(values)
});
} catch (error) {
console.log('Failed:', error);
}
};
And the /api/contact
import nodemailer from 'nodemailer';
export default async (req, res) => {
const { first_name, last_name, email, phone_number, subject, message } = req.body;
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL,
pass: process.env.PASSWORD
}
});
await new Promise((resolve, reject) => {
transporter.verify(function (error, success) {
if (error) {
console.log(error);
reject(error);
} else {
console.log('Server is ready to take our messages');
resolve(success);
}
});
});
const mailOption = {
from: `${email}`,
replyTo: `${email}`,
to: `${process.env.EMAIL}`,
subject: `${subject}`,
html: `<p><strong>Name:</strong> ${first_name + ' ' + last_name}</p>
<p><strong>Email:</strong> ${email}</p>
<p><strong>Phone number:</strong> ${phone_number}</p>
<p><strong>Message:</strong><br /> ${message}</p>
`
};
await new Promise((resolve, reject) => {
transporter.sendMail(mailOption, (err, info) => {
if (err) {
console.error(err);
reject(err);
res.send('error:' + JSON.stringify(err));
} else {
console.log(info);
resolve(info);
res.send('success');
}
});
});
res.status(200).json({ status: 'OK' });
};

Next Auth custom provider OIDC nonce check

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,
}
},
},
],

Resources