Skip to content

Commit 2fb01d3

Browse files
jfrelikatinux
andauthored
feat: added Microsoft as oauth provider (#8)
* 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]>
1 parent e7a0dbd commit 2fb01d3

File tree

9 files changed

+184
-3
lines changed

9 files changed

+184
-3
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,13 @@ It can also be set using environment variables:
150150
#### Supported OAuth Providers
151151

152152
- Auth0
153+
- Battle.net
153154
- Discord
154155
- GitHub
155156
- Google
157+
- Microsoft
156158
- Spotify
157159
- Twitch
158-
- Battle.net
159160

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

playground/.env.example

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@ NUXT_OAUTH_TWITCH_CLIENT_SECRET=
1515
NUXT_OAUTH_AUTH0_CLIENT_ID=
1616
NUXT_OAUTH_AUTH0_CLIENT_SECRET=
1717
NUXT_OAUTH_AUTH0_DOMAIN=
18+
# Microsoft OAuth
19+
NUXT_OAUTH_MICROSOFT_CLIENT_ID=
20+
NUXT_OAUTH_MICROSOFT_CLIENT_SECRET=
21+
NUXT_OAUTH_MICROSOFT_TENANT=
1822
# Discord
1923
NUXT_OAUTH_DISCORD_CLIENT_ID=
2024
NUXT_OAUTH_DISCORD_CLIENT_SECRET=
2125
# Battle.net OAuth
2226
NUXT_OAUTH_BATTLEDOTNET_CLIENT_ID=
23-
NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET=
27+
NUXT_OAUTH_BATTLEDOTNET_CLIENT_SECRET=

playground/app.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ const providers = computed(() => [
4444
disabled: Boolean(user.value?.battledotnet),
4545
icon: 'i-simple-icons-battledotnet',
4646
},
47+
{
48+
label: user.value?.microsoft?.displayName || 'Microsoft',
49+
to: '/auth/microsoft',
50+
disabled: Boolean(user.value?.microsoft),
51+
icon: 'i-simple-icons-microsoft',
52+
}
53+
4754
].map(p => ({
4855
...p,
4956
prefetch: false,

playground/auth.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ declare module '#auth-utils' {
66
google?: any
77
twitch?: any
88
auth0?: any
9+
microsoft?: any;
910
discord?: any
1011
battledotnet?: any
1112
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export default oauth.microsoftEventHandler({
2+
async onSuccess(event, { user }) {
3+
await setUserSession(event, {
4+
user: {
5+
microsoft: user,
6+
},
7+
loggedInAt: Date.now()
8+
})
9+
10+
return sendRedirect(event, '/')
11+
}
12+
})
13+

src/module.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ export default defineNuxtModule<ModuleOptions>({
9898
clientSecret: '',
9999
domain: ''
100100
})
101+
// Microsoft OAuth
102+
runtimeConfig.oauth.microsoft = defu(runtimeConfig.oauth.microsoft, {
103+
clientId: '',
104+
clientSecret: '',
105+
tenant: '',
106+
scope: [],
107+
authorizationURL: '',
108+
tokenURL: '',
109+
userURL: ''
110+
})
101111
// Discord OAuth
102112
runtimeConfig.oauth.discord = defu(runtimeConfig.oauth.discord, {
103113
clientId: '',

src/runtime/server/lib/oauth/discord.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ export function discordEventHandler({ config, onSuccess, onError }: OAuthConfig<
109109
return { error }
110110
})
111111
if (tokens.error) {
112-
console.log(tokens)
113112
const error = createError({
114113
statusCode: 401,
115114
message: `Discord login failed: ${tokens.error?.data?.error_description || 'Unknown error'}`,
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import type { H3Event, H3Error } from 'h3'
2+
import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from 'h3'
3+
import { withQuery, parsePath } from 'ufo'
4+
import { ofetch } from 'ofetch'
5+
import { defu } from 'defu'
6+
import { useRuntimeConfig } from '#imports'
7+
8+
export interface OAuthMicrosoftConfig {
9+
/**
10+
* Microsoft OAuth Client ID
11+
* @default process.env.NUXT_OAUTH_MICROSOFT_CLIENT_ID
12+
*/
13+
clientId?: string
14+
/**
15+
* Microsoft OAuth Client Secret
16+
* @default process.env.NUXT_OAUTH_MICROSOFT_CLIENT_SECRET
17+
*/
18+
clientSecret?: string
19+
/**
20+
* Microsoft OAuth Tenant ID
21+
* @default process.env.NUXT_OAUTH_MICROSOFT_TENANT
22+
*/
23+
tenant?: string
24+
/**
25+
* Microsoft OAuth Scope
26+
* @default ['User.Read']
27+
* @see https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc
28+
*/
29+
scope?: string[]
30+
/**
31+
* Microsoft OAuth Authorization URL
32+
* @default https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize
33+
* @see https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
34+
*/
35+
authorizationURL?: string
36+
/**
37+
* Microsoft OAuth Token URL
38+
* @default https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token
39+
* @see https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
40+
*/
41+
tokenURL?: string
42+
/**
43+
* Microsoft OAuth User URL
44+
* @default https://graph.microsoft.com/v1.0/me
45+
* @see https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http
46+
*/
47+
userURL?: string
48+
}
49+
50+
interface OAuthConfig {
51+
config?: OAuthMicrosoftConfig
52+
onSuccess: (event: H3Event, result: { user: any, tokens: any }) => Promise<void> | void
53+
onError?: (event: H3Event, error: H3Error) => Promise<void> | void
54+
}
55+
56+
export function microsoftEventHandler({ config, onSuccess, onError }: OAuthConfig) {
57+
return eventHandler(async (event: H3Event) => {
58+
// @ts-ignore
59+
config = defu(config, useRuntimeConfig(event).oauth?.microsoft) as OAuthMicrosoftConfig
60+
const { code } = getQuery(event)
61+
62+
if (!config.clientId || !config.clientSecret || !config.tenant) {
63+
const error = createError({
64+
statusCode: 500,
65+
message: 'Missing NUXT_OAUTH_MICROSOFT_CLIENT_ID or NUXT_OAUTH_MICROSOFT_CLIENT_SECRET or NUXT_OAUTH_MICROSOFT_TENANT env variables.'
66+
})
67+
if (!onError) throw error
68+
return onError(event, error)
69+
}
70+
71+
const authorizationURL = config.authorizationURL || `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/authorize`
72+
const tokenURL = config.tokenURL || `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/token`
73+
74+
const redirectUrl = getRequestURL(event).href
75+
if (!code) {
76+
77+
const scope = config.scope && config.scope.length > 0 ? config.scope : ['User.Read']
78+
// Redirect to Microsoft Oauth page
79+
return sendRedirect(
80+
event,
81+
withQuery(authorizationURL as string, {
82+
client_id: config.clientId,
83+
response_type: 'code',
84+
redirect_uri: redirectUrl,
85+
scope: scope.join('%20'),
86+
})
87+
)
88+
}
89+
90+
const data = new URLSearchParams()
91+
data.append('grant_type', 'authorization_code')
92+
data.append('client_id', config.clientId)
93+
data.append('client_secret', config.clientSecret)
94+
data.append('redirect_uri', parsePath(redirectUrl).pathname)
95+
data.append('code', String(code))
96+
97+
const tokens: any = await ofetch(
98+
tokenURL as string,
99+
{
100+
method: 'POST',
101+
headers: {
102+
'Content-Type': 'application/x-www-form-urlencoded',
103+
},
104+
body: data,
105+
}
106+
).catch(error => {
107+
return { error }
108+
})
109+
if (tokens.error) {
110+
const error = createError({
111+
statusCode: 401,
112+
message: `Microsoft login failed: ${tokens.error?.data?.error_description || 'Unknown error'}`,
113+
data: tokens
114+
})
115+
if (!onError) throw error
116+
return onError(event, error)
117+
}
118+
119+
const tokenType = tokens.token_type
120+
const accessToken = tokens.access_token
121+
const userURL = config.userURL || 'https://graph.microsoft.com/v1.0/me'
122+
const user: any = await ofetch(userURL, {
123+
headers: {
124+
Authorization: `${tokenType} ${accessToken}`
125+
}
126+
}).catch(error => {
127+
return { error }
128+
})
129+
if (user.error) {
130+
const error = createError({
131+
statusCode: 401,
132+
message: `Microsoft login failed: ${user.error || 'Unknown error'}`,
133+
data: user
134+
})
135+
if (!onError) throw error
136+
return onError(event, error)
137+
}
138+
139+
return onSuccess(event, {
140+
tokens,
141+
user
142+
})
143+
})
144+
}

src/runtime/server/utils/oauth.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { googleEventHandler } from '../lib/oauth/google'
33
import { spotifyEventHandler } from '../lib/oauth/spotify'
44
import { twitchEventHandler } from '../lib/oauth/twitch'
55
import { auth0EventHandler } from '../lib/oauth/auth0'
6+
import { microsoftEventHandler} from '../lib/oauth/microsoft'
67
import { discordEventHandler } from '../lib/oauth/discord'
78
import { battledotnetEventHandler } from '../lib/oauth/battledotnet'
89

@@ -12,6 +13,7 @@ export const oauth = {
1213
googleEventHandler,
1314
twitchEventHandler,
1415
auth0EventHandler,
16+
microsoftEventHandler,
1517
discordEventHandler,
1618
battledotnetEventHandler
1719
}

0 commit comments

Comments
 (0)