Skip to content

feat: added aws cognito provider #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ It can also be set using environment variables:
#### Supported OAuth Providers

- Auth0
- AWS Cognito
- Battle.net
- Discord
- GitHub
Expand Down
5 changes: 5 additions & 0 deletions playground/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ NUXT_OAUTH_KEYCLOAK_REALM=
# LinkedIn
NUXT_OAUTH_LINKEDIN_CLIENT_ID=
NUXT_OAUTH_LINKEDIN_CLIENT_SECRET=
# Cognito
NUXT_OAUTH_COGNITO_USER_POOL_ID=
NUXT_OAUTH_COGNITO_CLIENT_ID=
NUXT_OAUTH_COGNITO_CLIENT_SECRET=
NUXT_OAUTH_COGNITO_REGION=
6 changes: 6 additions & 0 deletions playground/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ const providers = computed(() => [
to: '/auth/linkedin',
disabled: Boolean(user.value?.linkedin),
icon: 'i-simple-icons-linkedin',
},
{
label: user.value?.cognito?.email || 'Cognito',
to: '/auth/cognito',
disabled: Boolean(user.value?.cognito),
icon: 'i-simple-icons-amazonaws',
}
].map(p => ({
...p,
Expand Down
12 changes: 12 additions & 0 deletions playground/server/routes/auth/cognito.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default oauth.cognitoEventHandler({
async onSuccess(event, { user }) {
await setUserSession(event, {
user: {
cognito: user,
},
loggedInAt: Date.now()
})

return sendRedirect(event, '/')
}
})
7 changes: 7 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,12 @@ export default defineNuxtModule<ModuleOptions>({
clientId: '',
clientSecret: ''
})
// Cognito OAuth
runtimeConfig.oauth.cognito = defu(runtimeConfig.oauth.cognito, {
clientId: '',
clientSecret: '',
region: '',
userPoolId: ''
})
}
})
106 changes: 106 additions & 0 deletions src/runtime/server/lib/oauth/cognito.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { H3Event } 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'
import type { OAuthConfig } from '#auth-utils'

export interface OAuthCognitoConfig {
/**
* AWS Cognito App Client ID
* @default process.env.NUXT_OAUTH_COGNITO_CLIENT_ID
*/
clientId?: string
/**
* AWS Cognito App Client Secret
* @default process.env.NUXT_OAUTH_COGNITO_CLIENT_SECRET
*/
clientSecret?: string
/**
* AWS Cognito User Pool ID
* @default process.env.NUXT_OAUTH_COGNITO_USER_POOL_ID
*/
userPoolId?: string
/**
* AWS Cognito Region
* @default process.env.NUXT_OAUTH_COGNITO_REGION
*/
region?: string
/**
* AWS Cognito Scope
* @default []
*/
scope?: string[]
}

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

if (!config.clientId || !config.clientSecret || !config.userPoolId || !config.region) {
const error = createError({
statusCode: 500,
message: 'Missing NUXT_OAUTH_COGNITO_CLIENT_ID, NUXT_OAUTH_COGNITO_CLIENT_SECRET, NUXT_OAUTH_COGNITO_USER_POOL_ID, or NUXT_OAUTH_COGNITO_REGION env variables.'
})
if (!onError) throw error
return onError(event, error)
}

const authorizationURL = `https://${config.userPoolId}.auth.${config.region}.amazoncognito.com/oauth2/authorize`
const tokenURL = `https://${config.userPoolId}.auth.${config.region}.amazoncognito.com/oauth2/token`

const redirectUrl = getRequestURL(event).href
if (!code) {
config.scope = config.scope || ['openid', 'profile']
// Redirect to Cognito login page
return sendRedirect(
event,
withQuery(authorizationURL as string, {
client_id: config.clientId,
redirect_uri: redirectUrl,
response_type: 'code',
scope: config.scope.join(' '),
})
)
}

const tokens: any = await ofetch(
tokenURL as string,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `grant_type=authorization_code&client_id=${config.clientId}&client_secret=${config.clientSecret}&redirect_uri=${parsePath(redirectUrl).pathname}&code=${code}`,
}
).catch(error => {
return { error }
})

if (tokens.error) {
const error = createError({
statusCode: 401,
message: `Cognito login failed: ${tokens.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://${config.userPoolId}.auth.${config.region}.amazoncognito.com/oauth2/userInfo`, {
headers: {
Authorization: `${tokenType} ${accessToken}`
}
})

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 @@ -8,6 +8,7 @@ import { discordEventHandler } from '../lib/oauth/discord'
import { battledotnetEventHandler } from '../lib/oauth/battledotnet'
import { keycloakEventHandler } from '../lib/oauth/keycloak'
import { linkedinEventHandler } from '../lib/oauth/linkedin'
import { cognitoEventHandler } from '../lib/oauth/cognito'

export const oauth = {
githubEventHandler,
Expand All @@ -20,4 +21,5 @@ export const oauth = {
battledotnetEventHandler,
keycloakEventHandler,
linkedinEventHandler,
cognitoEventHandler
}