From fa153f0fe077f54a75a5d251119169ebab3e43a3 Mon Sep 17 00:00:00 2001 From: Hernan Alvarado Date: Fri, 26 Jun 2026 17:14:27 +0000 Subject: [PATCH] feat(oidc): add Microsoft Identity OpenID Connect provider --- .../docs/(core)/oauth/microsoft-identity.mdx | 197 ++++++++++++++++++ packages/core/src/oauth/microsoft-identity.ts | 62 ++++++ packages/elysia/package.json | 2 +- packages/express/package.json | 2 +- packages/hono/package.json | 2 +- packages/next/package.json | 2 +- packages/react-router/package.json | 2 +- packages/react/package.json | 2 +- 8 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 docs/src/content/docs/(core)/oauth/microsoft-identity.mdx create mode 100644 packages/core/src/oauth/microsoft-identity.ts diff --git a/docs/src/content/docs/(core)/oauth/microsoft-identity.mdx b/docs/src/content/docs/(core)/oauth/microsoft-identity.mdx new file mode 100644 index 00000000..e27b47d5 --- /dev/null +++ b/docs/src/content/docs/(core)/oauth/microsoft-identity.mdx @@ -0,0 +1,197 @@ +--- +title: Microsoft Identity +description: Add Microsoft Identity authorization provider to Aura Auth for authentication and authorization +--- + + + + + +## Microsoft Identity OAuth App + +### Register the Application + +The first step is to create and register an OAuth App on the Microsoft Identity developer console to obtain access to the user's resources. + + + + + +## Installation + +Install the package using a package manager like `npm`, `pnpm`, or `yarn`: + +```npm +npm install @aura-stack/auth +``` + + + + + +## Environment setup + +Now, you must configure the environment variables required by Aura Auth, including the Microsoft Identity credentials and the encryption secrets. + +```bash title=".env" lineNumbers +# Aura Secrets +AURA_AUTH_SECRET="your-32-byte-secret" +AURA_AUTH_SALT="your-32-byte-salt" + +# Microsoft Identity Credentials +AURA_AUTH_MICROSOFT_IDENTITY_CLIENT_ID="your_microsoft_identity_client_id" +AURA_AUTH_MICROSOFT_IDENTITY_CLIENT_SECRET="your_microsoft_identity_client_secret" +``` + + + **CRITICAL SECURITY WARNING:** The `AURA_AUTH_SECRET` and `AURA_AUTH_SALT` variables are used to encrypt and sign user sessions. + These MUST be securely generated, highly randomized strings consisting of at least 32 bytes to ensure adequate entropy. Never + hardcode these values in your repository. Use a secure generator (like `openssl rand -base64 32`) to create them, and store them + exclusively in your secure environment variables manager. + + + + + + +## Configure the Auth Instance + +Configure the `createAuth` instance inside an `auth.ts` file located at the root of your project. Ensure you explicitly export the `handlers`, `api`, and `jose` objects. + +```ts title="auth.ts" lineNumbers +import { createAuth } from "@aura-stack/auth" + +export const auth = createAuth({ + oauth: ["microsoft-identity"], +}) + +// Extract the required utilities +export const { handlers, api, jose } = auth +``` + + + The `handlers` object contains mapping utilities for standard HTTP methods (`GET`, `POST`, `PATCH`) as well as a unified `ALL` + handler. This allows you to easily mount the authentication routes across any framework (Next.js, Elysia, Express, etc.). + + + + + + +## Customizing the OAuth Provider + +If you need to define custom scopes, change the response type, or map profile data differently, you can use the provider's factory function instead of a simple string identifier. + +```ts title="auth.ts" lineNumbers +import { createAuth } from "@aura-stack/auth" +import { microsoftIdentity } from "@aura-stack/auth/oauth/microsoft-identity" + +export const auth = createAuth({ + oauth: [ + microsoftIdentity({ + authorize: { + params: { + // Override default scopes + scope: "read:user user:email", + }, + }, + }), + ], +}) + +export const { handlers, api, jose } = auth +``` + + + + + +## Sign In to Microsoft Identity (Client & Server) + +There are multiple ways to trigger the sign-in flow depending on your ecosystem. + +### Sign-in Path (Direct Navigation) + +The common route to trigger the auth flow natively without needing a client library is simply navigating the browser to: +`http://localhost:3000/auth/signIn/microsoft-identity` + +--- + +### Client-Side (React, Vue, etc.) + +You can utilize the `createAuthClient` utility to programmatically trigger sign-ins. You can also define a `redirectTo` destination. + + + **Constraint Rule**: The `baseURL` passed into `createAuthClient` MUST exactly match the root domain and path where the HTTP + `handlers` expose their endpoints on the server. + + +```ts title="components/Login.tsx" lineNumbers +import { createAuthClient } from "@aura-stack/auth/client" + +export const authClient = createAuthClient({ + baseURL: "http://localhost:3000/auth", +}) + +const triggerSignIn = async () => { + await authClient.signIn("microsoft-identity", { + redirectTo: "/dashboard", + }) +} +``` + +--- + +### Server-Side (Next.js Actions, Remix Loaders, etc.) + +For environments supporting server-side actions, use the programmatic `api.signIn` method securely. + +```ts title="actions.ts" lineNumbers +import { api } from "./auth" + +export const serverSignIn = async () => { + const response = await api.signIn("microsoft-identity", { + redirectTo: "http://localhost:3000/dashboard", + }) + + // Example returning redirect location + return response.headers.get("Location") +} +``` + +--- + +### Session Retrieval + +After a user successfully signs in, you can retrieve their session data securely. + +**Client-Side:** + +```ts +const session = await authClient.getSession() +console.log(session?.user) // The authenticated Microsoft Identity user profile +``` + +**Server-Side:** + +```ts +// Note: You must pass the native Web Request object or Headers! +const session = await api.getSession(request) +console.log(session?.user) // Safely retrieved backend session +``` + + + + + +--- + +## Resources + +- [RFC - The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749) +- [Microsoft Identity Platform - OpenID Connect](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc) +- [Microsoft Identity Platform - OpenID Connect Metadata Document](https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration) +- [Microsoft Identity Platform - OpenID Connect ID Token Claims](https://learn.microsoft.com/en-us/entra/identity-platform/id-tokens) +- [Microsoft Identity Platform - OpenID Connect Scopes](https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc) +- [Microsoft Identity Platform - UserInfo Endpoint](https://learn.microsoft.com/en-us/entra/identity-platform/userinfo) +- [Microsoft Identity Platform - ID Token Claims Reference](https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference) diff --git a/packages/core/src/oauth/microsoft-identity.ts b/packages/core/src/oauth/microsoft-identity.ts new file mode 100644 index 00000000..75598edf --- /dev/null +++ b/packages/core/src/oauth/microsoft-identity.ts @@ -0,0 +1,62 @@ +import type { OpenIDProvider, User } from "@/@types/index.ts" + +/** + * @see [Microsoft Identity Platform - UserInfo Endpoint](https://learn.microsoft.com/en-us/entra/identity-platform/userinfo) + * @see [Microsoft Identity Platform - ID Token Claims Reference](https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference) + */ +export interface MicrosoftIdentityProfile { + aud: string + iss: string + iat: number + idp: string + nbf: number + exp: number + c_hash: string + at_hash: string + preferred_username: string + email: string + nonce: string + oid: string + roles: string[] + rh: string + sub: string + sid: string + unique_name: string + uti: string + ver: string + hasgroups: boolean + name: string + family_name: string + given_name: string + picture: string +} + +export const MICROSOFT_IDENTITY_ISSUER = "https://login.microsoftonline.com/:tenantId/v2.0" + +/** + * Microsoft Identity Platform OpenID Connect Provider + * + * @see [Microsoft Identity Platform - OpenID Connect](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc) + * @see [Microsoft Identity Platform - OpenID Connect Metadata Document](https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration) + * @see [Microsoft Identity Platform - OpenID Connect ID Token Claims](https://learn.microsoft.com/en-us/entra/identity-platform/id-tokens) + * @see [Microsoft Identity Platform - OpenID Connect Scopes](https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc) + * @see [Microsoft Identity Platform - UserInfo Endpoint](https://learn.microsoft.com/en-us/entra/identity-platform/userinfo) + * @see [Microsoft Identity Platform - ID Token Claims Reference](https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference) + */ +export const microsoftIdentity = ( + options?: Partial> +): OpenIDProvider => { + return { + id: "microsoft-identity", + name: "Microsoft Identity", + issuer: MICROSOFT_IDENTITY_ISSUER, + profile: (profile) => + ({ + sub: profile.sub, + name: profile.name, + email: profile.email, + image: profile.picture, + }) as DefaultUser, + ...options, + } as OpenIDProvider +} diff --git a/packages/elysia/package.json b/packages/elysia/package.json index 5389ffe8..2a8be41e 100644 --- a/packages/elysia/package.json +++ b/packages/elysia/package.json @@ -104,4 +104,4 @@ "elysia": ">=1.0.0" }, "packageManager": "pnpm@10.15.0" -} \ No newline at end of file +} diff --git a/packages/express/package.json b/packages/express/package.json index d2bcfdfe..732d61c0 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -109,4 +109,4 @@ "express": ">=4.0.0" }, "packageManager": "pnpm@10.15.0" -} \ No newline at end of file +} diff --git a/packages/hono/package.json b/packages/hono/package.json index 32887f97..40b75c6e 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -107,4 +107,4 @@ "hono": ">=4.0.0" }, "packageManager": "pnpm@10.15.0" -} \ No newline at end of file +} diff --git a/packages/next/package.json b/packages/next/package.json index 59135c2f..8fe8e796 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -142,4 +142,4 @@ "react-dom": ">=19.0.0" }, "packageManager": "pnpm@10.15.0" -} \ No newline at end of file +} diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 1f7ea1d5..4b8f0f63 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -114,4 +114,4 @@ "react-router": ">=7.0.0" }, "packageManager": "pnpm@10.15.0" -} \ No newline at end of file +} diff --git a/packages/react/package.json b/packages/react/package.json index dad8503f..ae6d5244 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -126,4 +126,4 @@ "@types/react": ">=19.0.0" }, "packageManager": "pnpm@10.15.0" -} \ No newline at end of file +}