-
I'm having trouble configuring the 5.0.0-beta.25 to communicate with TenantID, also referenced as issuer-specific authentication. Starting a discussion with the group to see if there are any tips or suggestions. I've scoured the internet and tried many things. I'll work on an isolated project reproduction in coming days but ideas and suggestions are appreciated!! Here's my environment: System: Here's where the errors show in my console: export function decodeJwt(jwt) { node_modules/next-auth/node_modules/@auth/core/lib/actions/callback/oauth/callback.js
Here's my auth.ts with many trial/error comments: import NextAuth, { type DefaultSession } from 'next-auth';
import 'next-auth/jwt';
import { GitHubAuthConfig } from './auth.config.github';
import { MicrosoftEntraIDAuthConfig } from './auth.config.entra';
import { TypeORMAdapter } from '@auth/typeorm-adapter';
import * as customEntities from '@/entity/app-entities';
import { DEFAULT_SESSION_EXPIRY, DEFAULT_DEV_SESSION_EXPIRY } from './constants';
import { apiExternalAuth } from '@/lib/api-external-auth';
import { redirect } from 'next/navigation';
import MicrosoftEntraID from 'next-auth/providers/microsoft-entra-id';
/**
* 300 for 5min, 60 * 60 * 24 * 7 for 1 week, 60 * 60 * 24 for 1 day
*/
const sessionExpiry = process.env.NODE_ENV === 'development' ? DEFAULT_DEV_SESSION_EXPIRY : DEFAULT_SESSION_EXPIRY;
/**
*
* node_modules/next-auth/node_modules/@auth/core/src/lib/symbols.ts
* /**
* @internal
*
* Used to mark some providers for processing within the core library.
*
* **Do not use or you will be fired.**
*
*/
// function conformInternal(...args: Parameters<typeof fetch>): ReturnType<typeof fetch> {
// // @ts-expect-error `undici` has a `duplex` option
// return undici(args[0], { ...args[1], dispatcher })
// }
export const { handlers, auth, signIn, signOut } = NextAuth({
debug: true, // !!process.env.AUTH_DEBUG,
// ...MicrosoftEntraIDAuthConfig,
providers: [
MicrosoftEntraID({
conformInternal: false,
clientId: process.env.AUTH_MICROSOFT_ENTRA_ID_ID ?? '',
clientSecret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET ?? '',
tenantId: process.env.AUTH_MICROSOFT_ENTRA_ID_TENANT_ID ?? '',
issuer: process.env.AUTH_MICROSOFT_ENTRA_ID_TENANT_ID,
authorization: {
url: process.env.AUTH_MICROSOFT_ENTRA_ID_AUTHORIZATION_URL,
params: {
prompt: "select_account",
// prompt: 'consent',
// scope: 'openid profile email',
// response_type: 'id_token',
// response_mode: 'form_post',
// maxAge: 60,
// grant_type: "authorization_code",
},
},
// checks: ['none'],
// checks: ['pkce'],
checks: ['pkce', 'state'], // this is the last working version
// checks: ["pkce", "nonce", "state"],
// checks: ["pkce", "nonce"],
// authorization: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
// token: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
token: { url: process.env.AUTH_MICROSOFT_ENTRA_ID_TOKEN_URL },
// checks: ["pkce", "state"],
profile: (profile) => {
return {
id: profile.id,
name: profile.displayName,
email: profile.emails?.[0].address,
image: profile.photos?.[0].value,
// Add extra fields here as defined in the extended User object in types/next-auth-extensions.d.ts
// need to work out how to get the user's role from the profile
// provider: 'microsoft-entra-id',
// role: 'viewer',
};
},
}),
],
// adapter: TypeORMAdapter(
// {
// type: 'better-sqlite3',
// database: process.env.NODE_ENV === 'production' ? './data/app.db' : './src/data/app.db',
// // do not set synchronize to true in production; it will drop the database; you may need synchronize in development to create the database
// synchronize: true,
// logging: false,
// },
// { entities: customEntities },
// ),
// basePath: '/api/auth',
// pages: {
// signIn: '/auth/login',
// error: '/auth/error',
// },
session: {
strategy: 'jwt',
maxAge: sessionExpiry,
},
// jwt: {
// maxAge: sessionExpiry,
// async encode({ token, secret }) {
// // Custom encoding logic here
// return token;
// },
// async decode({ token, secret }) {
// // Custom decoding logic here
// return token;
// },
// },
callbacks: {
// async jwt({ token, user, account, profile, isNewUser }) {
async jwt({ token, account, user }) {
// Add custom claims or properties to the JWT token
console.log('BKR-----JWT token', token);
if (account && user) {
token.accessToken = account.access_token;
token.id = user.id;
token.role = user.role;
}
return token;
},
async authorized({ request, auth }) {
return true;
// AuthJS does not work with middleware due to Edge runtime issues
// const { pathname } = request.nextUrl;
// if (pathname === '/middleware-example') return !!auth;
// console.log('BKR-----authorized', auth);
// const isAuthenticated = !!auth?.user;
// console.log('BKR-----isAuthenticated', isAuthenticated);
// return isAuthenticated;
},
/*
* Debugging shows that this is called after the user has been authenticated in the authorize callback.
* We are including the authorize callback in the credentials provider.
* We are attempting to make the inclusion of the authorize callback file based on the provider.
* as by default, the authorize callback is not included in the credentials provider.
* this import name will be adjust per provider: import { AuthConfig as authConfig, providers } from './auth.config.creds';
*/
async signIn(params) {
const { user, account, profile, credentials } = params;
return true;
// if (user) {
// console.log('authorized user role', user.role);
// const userRole = await apiExternalAuth(user.email as string);
// const currentRole = userRole?.AccountStatus ?? 'Unknown';
// if (currentRole !== user.role) {
// console.log('User role for {user.email} changed since created in this application. Changed to:', currentRole);
// return '/auth/role-change';
// } else {
// console.log('User role has not changed since created in this application. Role:', currentRole);
// return true;
// }
// }
/**
* Add your own logic here to handle sign in
* Lets add a new user to the database if it does not exist
* And check the external API for profile info
*/
// Return true to continue the sign-in process, or false to interrupt it
// return true;
},
async session({ session, user }) {
if (session?.accessToken) session.accessToken = session.accessToken.toString();
// console.log('Session user role', user.role);
session.user.role = user.role ?? 'session';
session.somevalue = 'somevalue';
return session;
},
},
// experimental: { enableWebAuthn: true },
// Found method is renamed to "name": "authjs.pkce.code_verifier",
// cookies: {
// pkceCodeVerifier: {
// name: 'authjs.pkce.code_verifier',
// options: {
// httpOnly: true,
// sameSite: 'none',
// path: '/',
// secure: process.env.NODE_ENV === 'production',
// },
// },
// },
// secret: process.env.AUTH_SECRET,
});
declare module 'next-auth' {
interface User {
role?: string;
// By default, TypeScript merges new interface properties and overwrites existing ones.
// In this case, the default user properties will be overwritten,
// with the new ones defined above. To keep the default user properties,
// you need to add them back into the newly declared interface.
id?: string;
name?: string | null;
email?: string | null;
image?: string | null;
}
interface Session {
accessToken?: string;
somevalue?: string;
user: {
role?: string;
} & DefaultSession['user'];
}
}
declare module 'next-auth/jwt' {
interface JWT {
accessToken?: string;
id?: string;
role?: string;
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 6 replies
-
Looking at your code, I don't see anything significantly different than what I'm doing. However, I set up the provider - particularly the issuer - slightly differently. Maybe it's worth a try:
|
Beta Was this translation helpful? Give feedback.
Abandoned this approach specifically for entra-id due to the number of challenges getting this to work correctly.