@williamanjo/ally-6-microsoft
v1.5.0
Published
Microsoft OAuth2 (Azure AD / Entra ID) authentication driver for AdonisJS v7 and Ally v6
Maintainers
Readme
Ally Microsoft Driver for AdonisJS 7
Microsoft OAuth2 driver for AdonisJS 7 and Ally v6. Supports authentication via Microsoft Account, Azure AD, and Entra ID — with two auth methods: client secret or certificate (RS256 JWT).
Certificate authentication is the recommended method for production. It avoids rotating secrets and is required by some enterprise Azure policies.
Installation
npm install @williamanjo/ally-6-microsoftConfigure
node ace configure @williamanjo/ally-6-microsoftThe command is interactive — it will ask:
- Authentication method —
Client SecretorCertificate (RS256 JWT) - Scopes — multi-select with defaults pre-checked (
openid,profile,email,User.Read)
Based on your choices it will:
- Add only the relevant env vars to
.envand.env.example - Add env validations to
start/env.ts - Create
config/ally.tswith the correct driver block — or, if the file already exists, print the block to add manually
Authentication methods
Option A — Client secret (quick start)
Env vars added by configure:
MICROSOFT_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
MICROSOFT_CLIENT_SECRET=xxxxxxxxxxxxxxxx
MICROSOFT_CALLBACK_URL=http://localhost:3333/microsoft/callback
MICROSOFT_TENANT_ID=commonGenerated config/ally.ts:
import { defineConfig } from '@adonisjs/ally'
import env from '#start/env'
import { microsoft } from '@williamanjo/ally-6-microsoft'
const allyConfig = defineConfig({
microsoft: microsoft({
clientId: env.get('MICROSOFT_CLIENT_ID'),
clientSecret: env.get('MICROSOFT_CLIENT_SECRET'),
callbackUrl: env.get('MICROSOFT_CALLBACK_URL'),
tenantId: env.get('MICROSOFT_TENANT_ID'),
scopes: ['openid', 'profile', 'email', 'User.Read'],
})
})
export default allyConfigOption B — Certificate authentication (recommended for production)
No rotating secrets. Uses a self-signed certificate uploaded to Azure — the driver signs each token request with an RS256 JWT (client_assertion).
1. Generate certificate
# RSA private key
openssl genrsa -out private.key 2048
# Self-signed certificate (1 year)
openssl req -new -x509 -key private.key -out certificate.crt -days 365 \
-subj "/CN=my-app-name"Windows (Git Bash): prefix with MSYS_NO_PATHCONV=1 to prevent path conversion of /CN=:
MSYS_NO_PATHCONV=1 openssl req -new -x509 -key private.key -out certificate.crt -days 365 -subj "/CN=my-app-name"Windows (PowerShell): use //CN= instead:
openssl req -new -x509 -key private.key -out certificate.crt -days 365 -subj "//CN=my-app-name"2. Extract the thumbprint
openssl x509 -in certificate.crt -fingerprint -sha1 -noout
# SHA1 Fingerprint=A1:B2:C3:D4:E5:F6:A1:B2:C3:D4:E5:F6:A1:B2:C3:D4:E5:F6:A1:B2The driver accepts both formats — with or without colons.
3. Upload to Azure Portal
- Azure Portal → App Registrations → your app
- Certificates & secrets → Certificates tab
- Upload
certificate.crt— confirm the thumbprint matches
4. Set env vars
Env vars added by configure when you choose certificate auth:
MICROSOFT_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
MICROSOFT_CALLBACK_URL=http://localhost:3333/microsoft/callback
MICROSOFT_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
MICROSOFT_CERT_THUMBPRINT=A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2
MICROSOFT_CERT_PRIVATE_KEY=/etc/certs/private.key
MICROSOFT_CERT_PRIVATE_KEYmust be the absolute path to the key file on the server. Never put the key content directly in.env.
5. Register the driver
Generated config/ally.ts:
import { defineConfig } from '@adonisjs/ally'
import env from '#start/env'
import { microsoft } from '@williamanjo/ally-6-microsoft'
const allyConfig = defineConfig({
microsoft: microsoft({
clientId: env.get('MICROSOFT_CLIENT_ID'),
callbackUrl: env.get('MICROSOFT_CALLBACK_URL'),
tenantId: env.get('MICROSOFT_TENANT_ID'),
scopes: ['openid', 'profile', 'email', 'User.Read'],
certificate: {
privateKey: env.get('MICROSOFT_CERT_PRIVATE_KEY'),
thumbprint: env.get('MICROSOFT_CERT_THUMBPRINT'),
},
})
})
export default allyConfig
clientSecretis required unlesscertificateis provided. Passing neither throws at startup.
Usage
import type { HttpContext } from '@adonisjs/core/http'
export default class AuthController {
async redirect({ ally }: HttpContext) {
return ally.use('microsoft').redirect()
}
async callback({ ally }: HttpContext) {
const microsoft = ally.use('microsoft')
if (microsoft.accessDenied()) return 'Access denied'
if (microsoft.stateMisMatch()) return 'Request expired'
if (microsoft.hasError()) return microsoft.getError()
const user = await microsoft.user()
return {
id: user.id,
name: user.name,
email: user.email,
avatar: user.avatarUrl, // null by default — see fetchPhoto
}
}
}Routes
import router from '@adonisjs/core/services/router'
router.get('/microsoft/redirect', '#controllers/auth_controller.redirect')
router.get('/microsoft/callback', '#controllers/auth_controller.callback')Azure Portal setup
- App Registrations → New registration
- Add redirect URI:
http://localhost:3333/microsoft/callback - Copy Application (client) ID and Directory (tenant) ID
- Add either a client secret or upload a certificate under Certificates & secrets
Options
Profile photo (base64)
user.avatarUrl returns null by default. Enable with fetchPhoto: true:
microsoft({
// ...
fetchPhoto: true,
})Returns a base64 data URI (data:image/jpeg;base64,...) or null if the user has no photo. Requires the User.Read scope (included by default).
The content-type returned by Microsoft Graph is validated against an allowlist (image/jpeg, image/png, image/gif, image/webp) before the data URI is constructed. Any unexpected content-type returns null.
Custom scopes
microsoft({
// ...
scopes: ['openid', 'profile', 'email', 'User.Read', 'offline_access'],
})| Scope | Description |
|---|---|
| openid | Required for OAuth2 login |
| profile | Display name and basic profile |
| email | Email address |
| User.Read | Full profile of the signed-in user |
| User.ReadBasic.All | Basic profiles of all users |
| offline_access | Receive a refresh token |
Supported features
- OAuth2 Authorization Code flow
- Microsoft Account, Azure AD, and Entra ID login
- Client secret authentication
- Certificate authentication (RS256 JWT
client_assertion) — recommended for production - Multi-tenant support via
tenantId - Profile photo as base64 data URI (opt-in)
- Standard Ally field mapping (
id,name,email,avatarUrl,original)
Requirements
- Node.js 18+
- AdonisJS 7+
- @adonisjs/ally v6
License
MIT
