Skip to content

feat: add logto oauth connector #370

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

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
1,251 changes: 0 additions & 1,251 deletions CHANGELOG.md

This file was deleted.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"name": "nuxt-auth-utils",
"version": "0.5.16",
"name": "@loickal/nuxt-auth-utils",
"version": "0.5.25",
"description": "Add Authentication to Nuxt applications with secured & sealed cookies sessions.",
"repository": {
"type": "git",
"url": "git+https://github.com/atinux/nuxt-auth-utils.git"
"url": "git+https://github.com/loickal/nuxt-auth-utils.git"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com/@loickal"
},
"license": "MIT",
"type": "module",
8 changes: 8 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
@@ -424,5 +424,13 @@ export default defineNuxtModule<ModuleOptions>({
redirectURL: '',
clientId: '',
})
// Logto OAuth
runtimeConfig.oauth.logto = defu(runtimeConfig.oauth.logto, {
clientId: '',
clientSecret: '',
domain: '',
scope: [],
redirectURL: '',
})
},
})
121 changes: 121 additions & 0 deletions src/runtime/server/lib/oauth/logto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import type { H3Event } from 'h3'
import { eventHandler, getQuery, sendRedirect } from 'h3'
import { withQuery } from 'ufo'
import { defu } from 'defu'
import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from '../utils'
import { useRuntimeConfig, createError } from '#imports'
import type { OAuthConfig } from '#auth-utils'

export interface OAuthLogtoConfig {
/**
* Logto OAuth Client ID
* @default process.env.NUXT_OAUTH_LOGTO_CLIENT_ID
*/
clientId?: string
/**
* Logto OAuth Client Secret
* @default process.env.NUXT_OAUTH_LOGTO_CLIENT_SECRET
*/
clientSecret?: string
/**
* Logto OAuth Domain
* @example <your-logto-tenant>logto.app
* @default process.env.NUXT_OAUTH_LOGTO_DOMAIN
*/
domain?: string
/**
* Logto OAuth Scope
* @default ['openid', 'profile', 'email']
* @see https://docs.logto.io/quick-starts/passport#scopes-and-claims
* @example ['openid', 'profile', 'email']
*/
scope?: string[]
/**
* Redirect URL to allow overriding for situations like prod failing to determine public hostname
* @default process.env.NUXT_OAUTH_LOGTO_REDIRECT_URL or current URL
*/
redirectURL?: string
}

export function defineOAuthLogtoEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthLogtoConfig>) {
return eventHandler(async (event: H3Event) => {
config = defu(config, useRuntimeConfig(event).oauth?.logto) as OAuthLogtoConfig

const query = getQuery<{ code?: string, error?: string }>(event)

if (query.error) {
const error = createError({
statusCode: 401,
message: `Logto login failed: ${query.error || 'Unknown error'}`,
data: query,
})
if (!onError) throw error
return onError(event, error)
}

if (!config.clientId || !config.clientSecret || !config.domain) {
return handleMissingConfiguration(event, 'logto', ['clientId', 'clientSecret', 'domain'], onError)
}

const authorizationURL = `https://${config.domain}/oidc/auth`
const tokenURL = `https://${config.domain}/oidc/token`
const redirectURL = config.redirectURL || getOAuthRedirectURL(event)

if (!query.code) {
config.scope = config.scope || ['openid', 'profile', 'email']

// Redirect to Logto OAuth page
return sendRedirect(
event,
withQuery(authorizationURL, {
response_type: 'code',
client_id: config.clientId,
redirect_uri: redirectURL,
scope: config.scope.join(' '),
}),
)
}

const tokens = await requestAccessToken(tokenURL, {
headers: {
'Authorization': `Basic ${Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64')}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {
grant_type: 'authorization_code',
client_id: config.clientId,
redirect_uri: redirectURL,
code: query.code,
},
})

if (tokens.error) {
return handleAccessTokenErrorResponse(event, 'logto', tokens, onError)
}

const accessToken = tokens.access_token
// Fetch user info
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const user: any = await $fetch(`https://${config.domain}/oidc/me`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/json',
},
})

if (!user) {
const error = createError({
statusCode: 500,
message: 'Could not get Logto user',
data: tokens,
})
if (!onError) throw error
return onError(event, error)
}

return onSuccess(event, {
user,
tokens,
})
})
}