-
-
Save madsh93/b573b3d8f070e62eaebc5c53ae34e2cc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}, | |
}, | |
}); |
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
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