Skip to content

Instantly share code, notes, and snippets.

@madsh93

madsh93/[...].ts Secret

Created November 29, 2022 09:06
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save madsh93/b573b3d8f070e62eaebc5c53ae34e2cc to your computer and use it in GitHub Desktop.
Save madsh93/b573b3d8f070e62eaebc5c53ae34e2cc to your computer and use it in GitHub Desktop.
import CredentialsProvider from "next-auth/providers/credentials";
import { NuxtAuthHandler } from "#auth";
/**
* Takes a token, and returns a new token with updated
* `accessToken` and `accessTokenExpires`. If an error occurs,
* returns the old token and an error property
*/
async function refreshAccessToken(refreshToken: {
accessToken: string;
accessTokenExpires: string;
refreshToken: string;
}) {
try {
console.warn("trying to post to refresh token");
const refreshedTokens = await $fetch<{
data: {
access_token: string;
expires: number;
refresh_token: string;
};
} | null>("https://domain.directus.app/auth/refresh", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: {
refresh_token: refreshToken.refreshToken,
mode: "json",
},
});
if (!refreshedTokens || !refreshedTokens.data) {
console.warn("No refreshed tokens");
throw refreshedTokens;
}
console.warn("Refreshed tokens successfully");
return {
...refreshToken,
accessToken: refreshedTokens.data.access_token,
accessTokenExpires: Date.now() + refreshedTokens.data.expires,
refreshToken: refreshedTokens.data.refresh_token,
};
} catch (error) {
console.warn("Error refreshing token", error);
return {
...refreshToken,
error: "RefreshAccessTokenError",
};
}
}
export default NuxtAuthHandler({
// secret needed to run nuxt-auth in production mode (used to encrypt data)
secret: process.env.NUXT_SECRET,
providers: [
// @ts-ignore Import is exported on .default during SSR, so we need to call it this way. May be fixed via Vite at some point
CredentialsProvider.default({
// The name to display on the sign in form (e.g. 'Sign in with...')
name: "Credentials",
// 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.
// You can pass any HTML attribute to the <input> tag through the object.
credentials: {
email: { label: "Email", type: "text" },
password: { label: "Password", type: "password" },
},
async authorize(credentials: any) {
// You need to provide your own logic here that takes the credentials
// submitted and returns either a object representing a user or value
// that is false/null if the credentials are invalid.
// NOTE: THE BELOW LOGIC IS NOT SAFE OR PROPER FOR AUTHENTICATION!
try {
const payload = {
email: credentials.email,
password: credentials.password,
};
const userTokens = await $fetch<{
data: { access_token: string; expires: number; refresh_token: string };
} | null>("https://domain.directus.app/auth/login", {
method: "POST",
body: payload,
headers: {
"Content-Type": "application/json",
"Accept-Language": "en-US",
},
});
const userDetails = await $fetch<{
data: {
id: string;
email: string;
first_name: string;
last_name: string;
role: string;
phone?: string;
cvr?: string;
company_name?: string;
};
} | null>("https://domain.directus.app/users/me", {
method: "GET",
headers: {
"Content-Type": "application/json",
"Accept-Language": "en-US",
Authorization: `Bearer ${userTokens?.data?.access_token}`,
},
});
if (!userTokens || !userTokens.data || !userDetails || !userDetails.data) {
throw createError({
statusCode: 500,
statusMessage: "Next auth failed",
});
}
const user = {
id: userDetails.data.id,
email: userDetails.data.email,
firstName: userDetails.data.first_name,
lastName: userDetails.data.last_name,
role: userDetails.data.role,
phone: userDetails.data.phone,
cvr: userDetails.data.cvr,
companyName: userDetails.data.company_name,
accessToken: userTokens.data.access_token,
accessTokenExpires: Date.now() + userTokens.data.expires,
refreshToken: userTokens.data.refresh_token,
};
const allowedRoles = [
"53ed3a6a-b236-49aa-be72-f26e6e4857a0",
"d9b59a92-e85d-43e2-8062-7a1242a8fce6",
];
// Only allow admins and sales
if (!allowedRoles.includes(user.role)) {
throw createError({
statusCode: 403,
statusMessage: "Not allowed",
});
}
return user;
} catch (error) {
console.warn("Error logging in", error);
return null;
}
},
}),
],
session: {
strategy: "jwt",
},
callbacks: {
async jwt({ token, user, account }) {
if (account && user) {
console.warn("JWT callback", { token, user, account });
return {
...token,
...user,
};
}
// Handle token refresh before it expires of 15 minutes
if (token.accessTokenExpires && Date.now() > token.accessTokenExpires) {
console.warn("Token is expired. Getting a new");
return refreshAccessToken(token);
}
return token;
},
async session({ session, token }) {
console.warn("Calling async session", session, token);
session.user = {
...session.user,
...token,
};
return session;
},
},
});
@vanling
Copy link

vanling commented Jan 29, 2023

Thanks for this gist :) I kept getting issues with the refresh token not updating when expired. Currently got it working by rolling back next-auth to "next-auth": "4.17.0" was the version around the time this gist was created. Very happy this works now 👍

edit: Above did not work.

edit: following this made it work for me sidebase/nuxt-auth#200

@hadimajidi
Copy link

There is some error. FetchError: fetch failed ()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment