Skip to main content

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:

  1. User clicks "Sign in with Microsoft" on the login page.
  2. They are redirected to Microsoft's login page.
  3. Microsoft authenticates them and returns to the application.
  4. The application verifies the identity, finds the user in its database, and issues an internal session.
  5. 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

RequirementDetails
Azure SubscriptionAn active Microsoft Azure subscription with admin access
Microsoft Entra ID (Azure AD)Tenant already exists (standard with any Microsoft 365 / Azure subscription)
User accountsAll application users must have accounts in your Azure AD tenant
Application serverMust be accessible via HTTPS in production

Part 1 — Azure Portal Configuration

Step 1 — Register the Application

  1. Sign in to the Azure Portal.
  2. Search for and open Microsoft Entra ID.
  3. In the left sidebar, click App registrationsNew registration.
  4. Fill in the form:
    • Name: MOC-PCR System (or any name you prefer)
    • Supported account types: Choose one of:
      • Accounts in this organizational directory onlyrecommended 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
  5. Click Register.

Copy and save the following from the app overview page:

  • Application (client) ID
  • Directory (tenant) ID

Step 2 — Create a Client Secret

  1. Inside your registered app, go to Certificates & secretsClient secretsNew client secret.
  2. Set a description (e.g., MOC-PCR Production Secret) and an expiry (recommend 12 or 24 months).
  3. Click Add.
  4. Immediately copy the Value column — 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

  1. Go to API permissionsAdd a permissionMicrosoft GraphDelegated permissions.
  2. Add the following permissions:
    • openid
    • profile
    • email
  3. Click Add permissions.
  4. 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:

  1. Go to Token configurationAdd optional claim.
  2. Token type: ID.
  3. Add the following claims:
    • email
    • preferred_username (fallback if primary email is not set)
  4. 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-file warning: Do not wrap any values in quotes in your .env.production file when using docker run --env-file. Docker passes quote characters as part of the value, which will cause Azure to reject the secret with AADSTS7000215: 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

VariableExpected FormatWhere to Find
AZURE_TENANT_IDUUID (e.g. 10091248-3181-...)App registration overview → Directory (tenant) ID
AZURE_CLIENT_IDUUID (e.g. 3dd121a7-9c06-...)App registration overview → Application (client) ID
AZURE_CLIENT_SECRETAlphanumeric string with special chars (e.g. mXK8Q~8vJ...)Created in Step 2 → Value column only
APP_URLFull URL, no trailing slash (e.g. https://app.example.com)Your deployment domain; must match Azure redirect URI
JWT_SECRETLong 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:

ScenarioBehaviour
User logs in via SSO for the first timeTheir Azure Object ID (oid) is stored against their database record
User logs in again via SSOThe stored oid is verified to match — login succeeds
Email exists in DB but SSO oid does not matchLogin is rejected to prevent account hijacking
Email does not exist in DBLogin 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:

  1. Go to Azure Portal → App registrations → your app → Certificates & secrets.
  2. Create a new client secret (do not delete the old one yet).
  3. Update AZURE_CLIENT_SECRET in your environment variables with the new Value.
  4. Restart the application server.
  5. 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 pageLikely causeResolution
SSO authentication failedWrong client secret, expired secret, or misconfigured redirect URIVerify all three env vars; check redirect URI matches exactly
No account found for your emailUser's email is not in the application databaseAdmin must create the user account first
Azure account mismatchA different Azure account was used than the one originally linkedContact system administrator
SSO session expiredBrowser state cookie expired (10-min window)Click "Sign in with Microsoft" again
Your Microsoft account does not have an email configuredAzure AD user has no email claimIn Azure, configure email/UPN for that user account

Next Steps

With Azure SSO configured and all five environment variables noted, proceed to:

Azure Blob Storage Setup →

The AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET values will be reused in the Azure Email Setup guide — you do not need to create a second app registration.


Last updated: February 2026