Skip to content

Instantly share code, notes, and snippets.

@mcorkum
Created May 5, 2023 18:19
Show Gist options
  • Save mcorkum/553fe738a6a427c89470a163c7d1a066 to your computer and use it in GitHub Desktop.
Save mcorkum/553fe738a6a427c89470a163c7d1a066 to your computer and use it in GitHub Desktop.
Microsoft ADFS Provider for Resource in Next Auth with Types
// Import the required packages
import NextAuth, { NextAuthOptions } from 'next-auth';
import { JWT } from 'next-auth/jwt';
import jwt_decode from 'jwt-decode'; // Used for decoding the accessToken in account
// Define the types for the profile
interface ProfileProps {
aud: string;
iss: string;
iat: number;
nbf: number;
exp: number;
auth_time: number;
sub: string;
upn: string;
unique_name: string;
sid: string;
}
// Extend existing types for NextAuth
declare module 'next-auth' {
// Add an error property to the Session interface
export interface Session {
error: 'RefreshAccessTokenError' | unknown;
}
// Add the expires_at property to the Account interface
export interface Account {
expires_at: any;
}
}
// Define a new interface to extend the JWT interface
interface ExtendedTokenSet extends JWT {
expires_at: number;
}
// Extend the JWT interface from next-auth/jwt
declare module 'next-auth/jwt' {
interface JWT {
access_token?: string; // Optional access token property
expires_at: number;
refresh_token?: string; // Optional refresh token property
error?: 'RefreshAccessTokenError'; // Optional error property
}
}
// For more information on each option (and a full list of options) go to https://next-auth.js.org/configuration/options
export const authOptions: NextAuthOptions = {
// cufed configuration options
providers: [
{
clientId: process.env.NEXT_CU_CLIENT_ID,
clientSecret: process.env.NEXT_CU_CLIENT_SECRET,
id: 'xxx',
name: 'xxxx',
type: 'oauth',
token: process.env.OAUTH_URL + '/adfs/oauth2/token',
userinfo: process.env.OAUTH_URL + '/adfs/oauth2/',
issuer: process.env.OAUTH_ISSUER,
wellKnown:
process.env.OAUTH_URL + '/adfs/.well-known/openid-configuration',
idToken: true,
authorization: {
url: process.env.OAUTH_URL + '/adfs/oauth2/authorize/',
params: {
scope: process.env.OAUTH_SCOPE,
resource: process.env.OAUTH_RESOURCE,
},
},
profile(profile: ProfileProps) {
console.log(
'πŸš€ ~ file: [...nextauth].ts:72 ~ profile ~ profile:',
profile
);
return {
id: profile.sid,
name: profile.unique_name,
email: profile.upn,
};
},
},
],
callbacks: {
// This is where "token" in the application is set.
// Account is only here when cufed first authenticates.
async jwt({ token, user, account }) {
console.log('πŸš€ ~ file: [...nextauth].ts:88 ~ jwt ~ token:', token);
if (account) {
console.log('πŸš€ ~ file: [...nextauth].ts:87 ~ jwt ~ account:', account);
// Save the access token and refresh token in the JWT on the initial login
return {
name: user?.name, // Store the user's name
email: user?.email, // Store the user's email
access_token: account.access_token,
expires_at: Math.floor(Date.now() / 1000 + account.expires_at),
refresh_token: account.refresh_token,
};
} else if (Date.now() < token.expires_at * 1000) {
// If the access token has not expired yet, return it
console.log('πŸš€ ~ file: [...nextauth].ts:100', 'Token has not expired');
return token;
} else {
// If the access token has expired, try to refresh it
console.log(
'πŸš€ ~ file: [...nextauth].ts:100',
'Token has expired - refreshing'
);
try {
// Build the URL string to send to cufed
const params = new URLSearchParams({
client_id: process.env.NEXT_CU_CLIENT_ID ?? '',
client_secret: process.env.NEXT_CU_CLIENT_SECRET ?? '',
grant_type: 'refresh_token',
refresh_token: token.refresh_token ?? '',
});
// Ask cufed for a refresh token from the token endpoint
const response: Response = await fetch(
`${process.env.OAUTH_URL}/adfs/oauth2/token`,
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
method: 'POST',
}
);
// Store both tokens in a set
const tokens: ExtendedTokenSet = await response.json();
console.log(
'πŸš€ ~ file: [...nextauth].ts:129 ~ jwt ~ tokens:',
tokens
);
// if there's a problem with the refresh token, throw the
if (!response.ok) throw tokens;
return {
...token, // Keep the previous token properties
name: user?.name, // Store the user's name
email: user?.email, // Store the user's email
access_token: tokens.access_token,
expires_at: Math.floor(Date.now() / 1000 + tokens.expires_at),
refresh_token: tokens.refresh_token ?? token.refresh_token, // Fall back to old refresh token
};
} catch (error) {
console.error('Error refreshing access token', error);
// The error property will be used client-side to handle the refresh token error
return { ...token, error: 'RefreshAccessTokenError' as const };
}
}
},
// This is where session is being set. You can assign values from the token here.
async session({ session, token }) {
// Assign user properties from the token to the session
if (token) {
session.user = {
name: token.name,
email: token.email,
};
session.error = token.error;
}
console.log(
'πŸš€ ~ file: [...nextauth].ts:169 ~ session ~ session:',
session
);
return session;
},
},
};
export default NextAuth(authOptions);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment