Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"ofetch": "^1.3.4",
"ohash": "^1.1.3",
"pathe": "^1.1.2",
"uncrypto": "^0.1.3"
"uncrypto": "^0.1.3",
"jwt-decode": "^3.1.2"
},
"devDependencies": {
"@iconify-json/simple-icons": "^1.1.99",
Expand All @@ -55,4 +56,4 @@
"vitest": "^1.5.0",
"vue-tsc": "^2.0.13"
}
}
}
63 changes: 48 additions & 15 deletions src/runtime/server/lib/oauth/microsoft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from
import { withQuery, parsePath } from 'ufo'
import { ofetch } from 'ofetch'
import { defu } from 'defu'
import jwtDecode from 'jwt-decode'
import { useRuntimeConfig } from '#imports'

export interface OAuthMicrosoftConfig {
Expand Down Expand Up @@ -45,6 +46,13 @@ export interface OAuthMicrosoftConfig {
* @see https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http
*/
userURL?: string
/**
* Flag to call the "me" endpoint. May not be callable depending on scopes used.
* If not used, Name and Email will be parsed from the returned JWT token.
* @default false
* @see https://learn.microsoft.com/en-us/entra/identity-platform/access-tokens
*/
useUser?: boolean
/**
* Extra authorization parameters to provide to the authorization URL
* @see https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
Expand All @@ -69,7 +77,9 @@ export function microsoftEventHandler({ config, onSuccess, onError }: OAuthConfi
return eventHandler(async (event: H3Event) => {
config = defu(config, useRuntimeConfig(event).oauth?.microsoft, {
authorizationParams: {},
useUser: false,
}) as OAuthMicrosoftConfig

const { code } = getQuery(event)

if (!config.clientId || !config.clientSecret || !config.tenant) {
Expand Down Expand Up @@ -133,24 +143,47 @@ export function microsoftEventHandler({ config, onSuccess, onError }: OAuthConfi

const tokenType = tokens.token_type
const accessToken = tokens.access_token
const userURL = config.userURL || 'https://graph.microsoft.com/v1.0/me'

// TODO: improve typing
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const user: any = await ofetch(userURL, {
headers: {
Authorization: `${tokenType} ${accessToken}`,
},
}).catch((error) => {
return { error }
})
if (user.error) {
const error = createError({
statusCode: 401,
message: `Microsoft login failed: ${user.error || 'Unknown error'}`,
data: user,
let user: any = {}

if (config.useUser) {
const userURL = config.userURL || 'https://graph.microsoft.com/v1.0/me'
user = await ofetch(userURL, {
headers: {
Authorization: `${tokenType} ${accessToken}`,
},
}).catch((error) => {
return { error }
})
if (!onError) throw error
return onError(event, error)
if (user.error) {
const error = createError({
statusCode: 401,
message: `Microsoft login failed: ${user.error || 'Unknown error'}`,
data: user,
})
if (!onError) throw error
return onError(event, error)
}
}
else {
// use of any is required as MS has two token versions
// see: https://learn.microsoft.com/en-us/entra/identity-platform/access-tokens
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const decodedToken = jwtDecode<any>(accessToken)
const msJwtVersion: '1.0' | '2.0' = decodedToken.ver

if (msJwtVersion === '2.0') {
user.displayName = decodedToken.name
user.mail = decodedToken.preferred_username
}
else {
const firstName = decodedToken.given_name
const lastName = decodedToken.family_name
user.displayName = `${firstName} ${lastName}`
user.mail = decodedToken.unique_name
}
}

return onSuccess(event, {
Expand Down