Skip to content

Commit

Permalink
src: Add AZURE_AD_ALLOWED_PRINCIPALS support
Browse files Browse the repository at this point in the history
New environment variable AZURE_AD_ALLOWED_PRINCIPALS is a comma separated list
of Object IDs for Azure AD user/group principals that are allowed to log in to
the app. If the list is empty, all authenticated users are allowed to log in.

This feature utilizes the Microsoft Graph API endpoint /me/getMemberObjects.
  • Loading branch information
yaegashi committed Sep 27, 2023
1 parent 7826a9d commit 1dd218c
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 8 deletions.
64 changes: 56 additions & 8 deletions src/features/auth/auth-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import GitHubProvider from "next-auth/providers/github";
const configureIdentityProvider = () => {
const providers: Array<Provider> = [];

const adminEmails = process.env.ADMIN_EMAIL_ADDRESS?.split(",").map(email => email.toLowerCase().trim());
const adminEmails = process.env.ADMIN_EMAIL_ADDRESS?.split(",").map(email => email.toLowerCase().trim()).filter(email => email);
const azureAdAllowedPrincipals = process.env.AZURE_AD_ALLOWED_PRINCIPALS?.split(",").map(oid => oid.toLowerCase().trim()).filter(oid => oid);

if (process.env.AUTH_GITHUB_ID && process.env.AUTH_GITHUB_SECRET) {
providers.push(
Expand All @@ -16,7 +17,8 @@ const configureIdentityProvider = () => {
async profile(profile) {
const newProfile = {
...profile,
isAdmin: adminEmails?.includes(profile.email.toLowerCase())
isAdmin: adminEmails?.includes(profile.email.toLowerCase()),
isAllowed: true
}
return newProfile;
}
Expand All @@ -34,13 +36,56 @@ const configureIdentityProvider = () => {
clientId: process.env.AZURE_AD_CLIENT_ID!,
clientSecret: process.env.AZURE_AD_CLIENT_SECRET!,
tenantId: process.env.AZURE_AD_TENANT_ID!,
async profile(profile) {

authorization: {
params: {
// Add User.Read to reach the /me endpoint of Microsoft Graph
scope: 'email openid profile User.Read'
}
},
async profile(profile, tokens) {
let isAllowed = true
if (Array.isArray(azureAdAllowedPrincipals) && azureAdAllowedPrincipals.length > 0) {
try {
isAllowed = false
// POST https://graph.microsoft.com/v1.0/me/getMemberObjects
// It returns all IDs of principal objects which "me" is a member of (transitive)
// https://learn.microsoft.com/en-us/graph/api/directoryobject-getmemberobjects?view=graph-rest-1.0&tabs=http
var response = await fetch(
'https://graph.microsoft.com/v1.0/me/getMemberObjects',
{
method: 'POST',
headers: {
Authorization: `Bearer ${tokens.access_token}`,
'Content-Type': 'application/json'
},
body: '{"securityEnabledOnly":true}'
}
)
if (response.ok) {
var body = await response.json() as { value?: string[] }
var oids = body.value ?? []
if (profile.oid) {
// Append the object ID of user principal "me"
oids.push(profile.oid)
}
for (const principal of azureAdAllowedPrincipals) {
if (oids.includes(principal)) {
isAllowed = true
break
}
}
}
}
catch (e) {
console.log(e)
}
}
const newProfile = {
...profile,
// throws error without this - unsure of the root cause (https://stackoverflow.com/questions/76244244/profile-id-is-missing-in-google-oauth-profile-response-nextauth)
id: profile.sub,
isAdmin: adminEmails?.includes(profile.email.toLowerCase()) || adminEmails?.includes(profile.preferred_username.toLowerCase())
isAdmin: adminEmails?.includes(profile.email.toLowerCase()) || adminEmails?.includes(profile.preferred_username.toLowerCase()),
isAllowed
}
return newProfile;
}
Expand All @@ -54,15 +99,18 @@ export const options: NextAuthOptions = {
secret: process.env.NEXTAUTH_SECRET,
providers: [...configureIdentityProvider()],
callbacks: {
async jwt({token, user, account, profile, isNewUser, session}) {
async jwt({ token, user, account, profile, isNewUser, session }) {
if (user?.isAdmin) {
token.isAdmin = user.isAdmin
token.isAdmin = user.isAdmin
}
return token
},
async session({session, token, user }) {
async session({ session, token, user }) {
session.user.isAdmin = token.isAdmin as string
return session
},
async signIn({ user }) {
return user.isAllowed
}
},
session: {
Expand Down
1 change: 1 addition & 0 deletions src/types/next-auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ declare module "next-auth" {

interface User {
isAdmin: string
isAllowed: boolean
}

}

0 comments on commit 1dd218c

Please sign in to comment.