Azure SSO Authentication — Setup Guide
Application: MOC & PCR Management System
Auth Provider: Microsoft Entra ID (Azure Active Directory)
Audience: IT Administrator / System Owner
Overview
This application uses Microsoft Single Sign-On (SSO) as its sole authentication method. Users log in with their existing organizational Microsoft accounts — no separate passwords are managed by the application.
The flow is:
- User clicks "Sign in with Microsoft" on the login page.
- They are redirected to Microsoft's login page.
- Microsoft authenticates them and returns to the application.
- The application verifies the identity, finds the user in its database, and issues an internal session.
- The user is taken to their dashboard — the rest of the app works exactly as before.
No credentials are ever stored or transmitted through the application itself.
Prerequisites
| Requirement | Details |
|---|---|
| Azure Subscription | An active Microsoft Azure subscription with admin access |
| Microsoft Entra ID (Azure AD) | Tenant already exists (standard with any Microsoft 365 / Azure subscription) |
| User accounts | All application users must have accounts in your Azure AD tenant |
| Application server | Must be accessible via HTTPS in production |
Part 1 — Azure Portal Configuration
Step 1 — Register the Application
- Sign in to the Azure Portal.
- Search for and open Microsoft Entra ID.
- In the left sidebar, click App registrations → New registration.
- Fill in the form:
- Name:
MOC-PCR System(or any name you prefer) - Supported account types: Choose one of:
- Accounts in this organizational directory only — recommended for internal company use
- Accounts in any organizational directory — if multiple tenants need access
- Redirect URI:
- Platform: Web
- URI:
https://your-production-domain.com/api/auth/callback
- Name:
- Click Register.
Copy and save the following from the app overview page:
- Application (client) ID
- Directory (tenant) ID
Step 2 — Create a Client Secret
- Inside your registered app, go to Certificates & secrets → Client secrets → New client secret.
- Set a description (e.g.,
MOC-PCR Production Secret) and an expiry (recommend 12 or 24 months). - Click Add.
- Immediately copy the
Valuecolumn — it is shown only once.
⚠️ Important: Azure shows two things after creating a secret:
- Secret ID — a GUID identifier, used only in the Azure portal for management. Do not use this in your application.
- Value — the actual secret string (e.g.,
mXK8Q~...). This is what goes in your environment variables.
Step 3 — Configure API Permissions
- Go to API permissions → Add a permission → Microsoft Graph → Delegated permissions.
- Add the following permissions:
openidprofileemail
- Click Add permissions.
- Click Grant admin consent for [Your Organization] → Yes.
All three permissions must show a green ✓ Granted status.
Step 4 — Configure the Token
To ensure the email claim is included in the ID token:
- Go to Token configuration → Add optional claim.
- Token type: ID.
- Add the following claims:
emailpreferred_username(fallback if primary email is not set)
- Click Add. If prompted to add Microsoft Graph permissions, click Yes.
Part 2 — Application Environment Variables
On the server where the application is deployed, set the following environment variables. These must never be committed to source control.
# ── Microsoft Entra ID (Azure SSO) ─────────────────────────────────────────
AZURE_TENANT_ID=<Directory (tenant) ID from Step 1>
AZURE_CLIENT_ID=<Application (client) ID from Step 1>
AZURE_CLIENT_SECRET=<Secret VALUE from Step 2 — NOT the Secret ID>
# ── Application URL ─────────────────────────────────────────────────────────
# The full public URL of the application (no trailing slash).
# This MUST match the Redirect URI registered in Azure (Step 1, Part 1).
# Used to build the redirect_uri sent to Microsoft during login.
# Example for local: http://localhost:3000
# Example for production: https://mocpcr.yourcompany.com
APP_URL=<full public URL of the application>
# ── Session Security ─────────────────────────────────────────────────────────
# A secret string used to sign the internal session JWT.
# Generate a strong random value — run this command and copy the output:
# openssl rand -hex 32
# Once set in production, do NOT change this value — it will invalidate
# all existing sessions and log out all users immediately.
JWT_SECRET=<strong random secret string>
⚠️ Docker
--env-filewarning: Do not wrap any values in quotes in your.env.productionfile when usingdocker run --env-file. Docker passes quote characters as part of the value, which will cause Azure to reject the secret withAADSTS7000215: Invalid client secret. Write values without quotes:AZURE_CLIENT_SECRET=mXK8Q~8vJ5... ✔ correct
AZURE_CLIENT_SECRET="mXK8Q~8vJ5..." ✘ wrong — Docker includes the quotes
Verification Checklist
| Variable | Expected Format | Where to Find |
|---|---|---|
AZURE_TENANT_ID | UUID (e.g. 10091248-3181-...) | App registration overview → Directory (tenant) ID |
AZURE_CLIENT_ID | UUID (e.g. 3dd121a7-9c06-...) | App registration overview → Application (client) ID |
AZURE_CLIENT_SECRET | Alphanumeric string with special chars (e.g. mXK8Q~8vJ...) | Created in Step 2 → Value column only |
APP_URL | Full URL, no trailing slash (e.g. https://app.example.com) | Your deployment domain; must match Azure redirect URI |
JWT_SECRET | Long random string (min 32 chars recommended) | Generate with openssl rand -hex 32 |
Part 3 — Production Redirect URI
The Redirect URI configured in Azure (Step 1) must exactly match what the application sends during login. In production this is:
https://your-production-domain.com/api/auth/callback
If you are running on a non-standard port (e.g. during staging), the URI must include the port:
https://your-staging-domain.com:8080/api/auth/callback
To add or change redirect URIs: Azure Portal → App registrations → your app → Authentication → add under Web redirect URIs.
Part 4 — User Provisioning
Users are not created through Azure — they must be pre-provisioned in the application's database by the system administrator.
The linking process works as follows:
| Scenario | Behaviour |
|---|---|
| User logs in via SSO for the first time | Their Azure Object ID (oid) is stored against their database record |
| User logs in again via SSO | The stored oid is verified to match — login succeeds |
Email exists in DB but SSO oid does not match | Login is rejected to prevent account hijacking |
| Email does not exist in DB | Login is rejected with a 403. The admin must add the user first. |
To add a new user: Use the system's admin panel to create the user's account with their exact organizational email address before they attempt to log in.
Part 5 — Secret Rotation
Client secrets expire. Before the secret reaches its expiry date:
- Go to Azure Portal → App registrations → your app → Certificates & secrets.
- Create a new client secret (do not delete the old one yet).
- Update
AZURE_CLIENT_SECRETin your environment variables with the new Value. - Restart the application server.
- Confirm logins are working, then delete the old secret from Azure.
Recommended: Set a calendar reminder 30 days before the secret's expiry date.
Authentication Flow — Technical Reference
Browser Application Server Microsoft
| | |
|── Click "Sign in" ──────────▶| |
| |── Redirect to Azure ───────▶|
|◀─────────────────────────────|◀─── Redirect to /login ─────|
| | |
|── Navigate to Azure ────────────────────────────────────▶ |
| | User logs in |
|◀── Redirect to /api/auth/callback?code=... ─────────────── |
| | |
|─── code + state ────────────▶| |
| |── Exchange code for token ─▶|
| |◀── id_token ────────────────|
| | |
| | Validate token via JWKS |
| | (signature + issuer + exp) |
| | |
| | Find user in MongoDB |
| | by email |
| | |
| | Issue internal JWT (7 days) |
| | Set httpOnly session cookie |
| | |
|◀── Redirect to dashboard ────| |
The internal session JWT contains: _id, email, name, role, department, access, azureObjectId. All existing application authorization logic reads from this JWT and remains unchanged.
Troubleshooting
| Error shown on login page | Likely cause | Resolution |
|---|---|---|
SSO authentication failed | Wrong client secret, expired secret, or misconfigured redirect URI | Verify all three env vars; check redirect URI matches exactly |
No account found for your email | User's email is not in the application database | Admin must create the user account first |
Azure account mismatch | A different Azure account was used than the one originally linked | Contact system administrator |
SSO session expired | Browser state cookie expired (10-min window) | Click "Sign in with Microsoft" again |
Your Microsoft account does not have an email configured | Azure AD user has no email claim | In Azure, configure email/UPN for that user account |
Next Steps
With Azure SSO configured and all five environment variables noted, proceed to:
The
AZURE_TENANT_ID,AZURE_CLIENT_ID, andAZURE_CLIENT_SECRETvalues will be reused in the Azure Email Setup guide — you do not need to create a second app registration.
Last updated: February 2026