Skip to content

Commit

Permalink
feat: added Microsoft as oauth provider
Browse files Browse the repository at this point in the history
  • Loading branch information
jfrelik committed Nov 9, 2023
1 parent 86226ad commit 4837884
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 10 deletions.
9 changes: 7 additions & 2 deletions playground/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,16 @@ const { loggedIn, session, clear } = useUserSession()
Login with Auth0
</UButton>
<UButton
v-if="loggedIn"
v-if="!loggedIn || !session.user.microsoft"
to="/auth/microsoft"
icon="i-simple-icons-microsoft"
external
color="gray"
size="xs"
@click="clear"
>
Login with Microsoft
</UButton>
<UButton v-if="loggedIn" color="gray" size="xs" @click="clear">
Logout
</UButton>
</template>
Expand Down
15 changes: 8 additions & 7 deletions playground/auth.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
declare module '#auth-utils' {
interface UserSession {
user: {
spotify?: any
github?: any
google?: any
twitch?: any
auth0?: any
}
loggedInAt: number
spotify?: any;
github?: any;
google?: any;
twitch?: any;
auth0?: any;
microsoft?: any;
};
loggedInAt: number;
}
}
13 changes: 13 additions & 0 deletions playground/server/routes/auth/microsoft.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default oauth.microsoftEventHandler({
async onSuccess(event, { user }) {
await setUserSession(event, {
user: {
microsoft: user,
},
loggedInAt: Date.now()
})

return sendRedirect(event, '/')
}
})

6 changes: 6 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,11 @@ export default defineNuxtModule<ModuleOptions>({
clientSecret: '',
domain: ''
})
// Microsoft OAuth
runtimeConfig.oauth.microsoft = defu(runtimeConfig.oauth.microsoft, {
clientId: '',
clientSecret: '',
tenant: '',
})
}
})
114 changes: 114 additions & 0 deletions src/runtime/server/lib/oauth/microsoft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import type { H3Event, H3Error } from 'h3'
import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from 'h3'
import { withQuery, parsePath } from 'ufo'
import { ofetch } from 'ofetch'
import { defu } from 'defu'
import { useRuntimeConfig } from '#imports'

export interface OAuthMicrosoftConfig {
/**
* Microsoft OAuth Client ID
* @default process.env.NUXT_OAUTH_MICROSOFT_CLIENT_ID
*/
clientId?: string
/**
* Microsoft OAuth Client Secret
* @default process.env.NUXT_OAUTH_MICROSOFT_CLIENT_SECRET
*/
clientSecret?: string
/**
* Microsoft OAuth Tenant ID
* @default process.env.NUXT_OAUTH_MICROSOFT_TENANT
*/
tenant?: string
/**
* Microsoft OAuth Scope
* @default []
* @see https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc
* @example ['User.Read']
*/
scope?: string[]
}

interface OAuthConfig {
config?: OAuthMicrosoftConfig
onSuccess: (event: H3Event, result: { user: any, tokens: any }) => Promise<void> | void
onError?: (event: H3Event, error: H3Error) => Promise<void> | void
}

export function microsoftEventHandler({ config, onSuccess, onError }: OAuthConfig) {
return eventHandler(async (event: H3Event) => {
// @ts-ignore
config = defu(config, useRuntimeConfig(event).oauth?.microsoft) as OAuthMicrosoftConfig
const { code } = getQuery(event)

if (!config.clientId || !config.clientSecret || !config.tenant) {
const error = createError({
statusCode: 500,
message: 'Missing NUXT_OAUTH_MICROSOFT_CLIENT_ID or NUXT_OAUTH_MICROSOFT_CLIENT_SECRET or NUXT_OAUTH_MICROSOFT_TENANT env variables.'
})
if (!onError) throw error
return onError(event, error)
}
const authorizationURL = `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/authorize`
const tokenURL = `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/token`

const redirectUrl = getRequestURL(event).href
if (!code) {

config.scope = config.scope || ['User.Read']
// Redirect to Microsoft Oauth page
return sendRedirect(
event,
withQuery(authorizationURL as string, {
client_id: config.clientId,
response_type: 'code',
redirect_uri: redirectUrl,
scope: config.scope.join('%20'),
})
)
}

const data = new URLSearchParams()
data.append('grant_type', 'authorization_code')
data.append('client_id', config.clientId)
data.append('client_secret', config.clientSecret)
data.append('redirect_uri', parsePath(redirectUrl).pathname)
data.append('code', String(code))

const tokens: any = await ofetch(
tokenURL as string,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: data,
}
).catch(error => {
return { error }
})
if (tokens.error) {
const error = createError({
statusCode: 401,
message: `Microsoft login failed: ${tokens.error?.data?.error_description || 'Unknown error'}`,
data: tokens
})
if (!onError) throw error
return onError(event, error)
}

const tokenType = tokens.token_type
const accessToken = tokens.access_token
const user: any = await ofetch('https://graph.microsoft.com/v1.0/me', {
headers: {
Authorization: `${tokenType} ${accessToken}`
}
})

return onSuccess(event, {
tokens,
user
})
})
}
4 changes: 3 additions & 1 deletion src/runtime/server/utils/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { googleEventHandler } from '../lib/oauth/google'
import { spotifyEventHandler } from '../lib/oauth/spotify'
import { twitchEventHandler } from '../lib/oauth/twitch'
import { auth0EventHandler } from '../lib/oauth/auth0'
import { microsoftEventHandler} from '../lib/oauth/microsoft'

export const oauth = {
githubEventHandler,
spotifyEventHandler,
googleEventHandler,
twitchEventHandler,
auth0EventHandler
auth0EventHandler,
microsoftEventHandler
}

0 comments on commit 4837884

Please sign in to comment.