Skip to content

Commit

Permalink
feat: added Microsoft as oauth provider (#8)
Browse files Browse the repository at this point in the history
* feat: added Microsoft as oauth provider

* feat: added Microsoft OAuth configuration to .env.example

* feat: Added support for Azure Government

* feat: Added usGov env to module config

* fix: discord login button

* feat: Microsoft login error handling

* feat: Update Microsoft OAuth configuration

* chore: remove extra logs

* add microsoft

---------

Co-authored-by: Sébastien Chopin <[email protected]>
  • Loading branch information
jfrelik and atinux authored Nov 29, 2023
1 parent e7a0dbd commit 2fb01d3
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 3 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,13 @@ It can also be set using environment variables:
#### Supported OAuth Providers

- Auth0
- Battle.net
- Discord
- GitHub
- Google
- Microsoft
- Spotify
- Twitch
- Battle.net

You can add your favorite provider by creating a new file in [src/runtime/server/lib/oauth/](./src/runtime/server/lib/oauth/).

Expand Down
6 changes: 5 additions & 1 deletion playground/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ NUXT_OAUTH_TWITCH_CLIENT_SECRET=
NUXT_OAUTH_AUTH0_CLIENT_ID=
NUXT_OAUTH_AUTH0_CLIENT_SECRET=
NUXT_OAUTH_AUTH0_DOMAIN=
# Microsoft OAuth
NUXT_OAUTH_MICROSOFT_CLIENT_ID=
NUXT_OAUTH_MICROSOFT_CLIENT_SECRET=
NUXT_OAUTH_MICROSOFT_TENANT=
# Discord
NUXT_OAUTH_DISCORD_CLIENT_ID=
NUXT_OAUTH_DISCORD_CLIENT_SECRET=
# Battle.net OAuth
NUXT_OAUTH_BATTLEDOTNET_CLIENT_ID=
NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET=
NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET=
7 changes: 7 additions & 0 deletions playground/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ const providers = computed(() => [
disabled: Boolean(user.value?.battledotnet),
icon: 'i-simple-icons-battledotnet',
},
{
label: user.value?.microsoft?.displayName || 'Microsoft',
to: '/auth/microsoft',
disabled: Boolean(user.value?.microsoft),
icon: 'i-simple-icons-microsoft',
}
].map(p => ({
...p,
prefetch: false,
Expand Down
1 change: 1 addition & 0 deletions playground/auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ declare module '#auth-utils' {
google?: any
twitch?: any
auth0?: any
microsoft?: any;
discord?: any
battledotnet?: any
}
Expand Down
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, '/')
}
})

10 changes: 10 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ export default defineNuxtModule<ModuleOptions>({
clientSecret: '',
domain: ''
})
// Microsoft OAuth
runtimeConfig.oauth.microsoft = defu(runtimeConfig.oauth.microsoft, {
clientId: '',
clientSecret: '',
tenant: '',
scope: [],
authorizationURL: '',
tokenURL: '',
userURL: ''
})
// Discord OAuth
runtimeConfig.oauth.discord = defu(runtimeConfig.oauth.discord, {
clientId: '',
Expand Down
1 change: 0 additions & 1 deletion src/runtime/server/lib/oauth/discord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ export function discordEventHandler({ config, onSuccess, onError }: OAuthConfig<
return { error }
})
if (tokens.error) {
console.log(tokens)
const error = createError({
statusCode: 401,
message: `Discord login failed: ${tokens.error?.data?.error_description || 'Unknown error'}`,
Expand Down
144 changes: 144 additions & 0 deletions src/runtime/server/lib/oauth/microsoft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
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 ['User.Read']
* @see https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc
*/
scope?: string[]
/**
* Microsoft OAuth Authorization URL
* @default https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize
* @see https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
*/
authorizationURL?: string
/**
* Microsoft OAuth Token URL
* @default https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token
* @see https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
*/
tokenURL?: string
/**
* Microsoft OAuth User URL
* @default https://graph.microsoft.com/v1.0/me
* @see https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http
*/
userURL?: 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 = config.authorizationURL || `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/authorize`
const tokenURL = config.tokenURL || `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/token`

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

const scope = config.scope && config.scope.length > 0 ? 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: 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 userURL = config.userURL || 'https://graph.microsoft.com/v1.0/me'
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
})
if (!onError) throw error
return onError(event, error)
}

return onSuccess(event, {
tokens,
user
})
})
}
2 changes: 2 additions & 0 deletions src/runtime/server/utils/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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'
import { discordEventHandler } from '../lib/oauth/discord'
import { battledotnetEventHandler } from '../lib/oauth/battledotnet'

Expand All @@ -12,6 +13,7 @@ export const oauth = {
googleEventHandler,
twitchEventHandler,
auth0EventHandler,
microsoftEventHandler,
discordEventHandler,
battledotnetEventHandler
}

0 comments on commit 2fb01d3

Please sign in to comment.