Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

VouchGate



Self-service guest access requests for Microsoft Entra ID with manager approval. A lightweight alternative to Entitlement Management. No Governance licenses required.

Version License: MIT Azure Static Web Apps Node.js 24

Guest access with a sponsor. Nothing gets in without a vouch.

Built on Azure Static Web Apps + Azure Functions, secured with Entra ID. Microsoft Graph is called via a system-assigned Managed Identity, so there are no stored secrets.


✨ Features

  • πŸ™‹ Self-service requests: internal users request time-boxed guest (B2B) access without a helpdesk ticket
  • βœ… Manager approval: the requester's manager is resolved server-side via Microsoft Graph, with a configurable fallback approver group
  • ⏫ Approval escalation: if a request stays pending past a threshold, fallback approver group members can also approve it, and a daily digest reminds them
  • ⏱️ Time-boxed access: choose 30 / 90 / 180 / 365 days; the approval expiry is stamped on the guest as extensionAttribute13
  • πŸ“¬ Portal-only approvals: approvers get a notification mail that links into the portal; no unauthenticated magic links
  • 🧾 Full audit trail: every request and decision is written to Azure Table Storage with actor, timestamps and source IP
  • πŸ”‘ Managed Identity: no stored secrets for Graph access
  • πŸ›‘οΈ Easy Auth: Entra ID JWT validated by Azure before your code runs; invalid or missing tokens rejected with 401 automatically
  • 🎨 CSS theming: full white-labeling via CSS custom properties, with a light/dark theme toggle
  • ♻️ Lifecycle-friendly: expiry cleanup is delegated to an external lifecycle project; VouchGate never excludes its guests from it
  • 🌐 Custom domain: supported via Azure Static Web Apps (Standard tier)

🧩 How it works

  1. πŸ‘€ An internal user signs in with their Entra ID account
  2. πŸ“ They submit a guest access request (guest email, display name, optional company, justification, duration)
  3. 🧭 The backend resolves the manager (/users/{id}/manager), or falls back to a configured approver group if no manager is set
  4. πŸ“¨ The approver receives a notification mail with a link into the portal
  5. πŸ” The approver signs in, reviews pending requests, and approves or denies (with an optional comment)
  6. πŸšͺ On approval, a Function creates the guest (POST /invitations) and stamps extensionAttribute13 (expiry), employeeType = "VouchGate" and sponsors (the requester); the requester is notified that sharing is now possible
  7. ❌ On denial, the requester is notified with the reason
  8. 🧾 Every step is written to the audit table

πŸ‘₯ For approvers

Approvers use the same portal URL as everyone else. There is no separate admin area or admin login.

  • πŸ” After signing in, an Approver Inbox tab appears automatically whenever there are pending requests assigned to the signed-in user, either as the resolved manager of the requester, or as a fallback approver group member once a request has escalated (pending past ESCALATION_DAYS). If no requests are assigned, the tab is not shown.
  • πŸ”— The link in the notification mail (/?request={id}) is a convenience deep link that opens and highlights that specific request. It is not the only way in: approvers can always open the portal directly and use the Approver Inbox tab to see every request assigned to them.

⚠️ If "Assignment required" is enabled on the app, approvers must also be assigned. Managers (and fallback approver group members) sign in to the same app as requesters, so if only requesters are assigned, managers cannot sign in to approve. Assign a group that covers both requesters and approvers, or add the approvers' group to the app assignment. See Restricting access.

No admin dashboard (v1)

VouchGate v1 has no admin dashboard. The full audit trail is stored in the VouchGateAudit table in the deployment's storage account, with one record per request submission and per approval/denial (actor, approverType, guest email, comment, source IP and timestamp). View it through the Azure Portal (Storage account β†’ Storage browser β†’ Tables β†’ VouchGateAudit) or Azure Storage Explorer.


πŸ“Έ Screenshots

Sign-in Request form
Sign-in screen Guest access request form
My requests Approver inbox
My requests list Approver inbox with pending requests

βœ… Prerequisites

πŸ› οΈ Tools

Tool Min. Version Install
Azure CLI 2.50 aka.ms/installazurecli
Node.js 24 LTS nodejs.org, includes npm
Static Web Apps CLI latest npm install -g @azure/static-web-apps-cli
Exchange Online PowerShell latest Install-Module ExchangeOnlineManagement (for the mail-scoping step)

πŸ” Azure & Entra permissions

Scope Role
Azure subscription Contributor
Entra ID tenant Application Administrator (or Global Administrator)
Entra ID tenant Privileged Role Administrator
Exchange Online Exchange Administrator (to scope Mail.Send, see below)

πŸ’‘ A Global Administrator covers the Entra roles by default. The Exchange Administrator role is required for the mandatory Mail.Send scoping step.


πŸš€ Deploy

🍎 macOS / Linux / WSL

1. Install prerequisites (once):

# macOS (Homebrew)
brew install azure-cli node
npm install -g @azure/static-web-apps-cli

2. Clone and deploy:

git clone https://github.com/daniel-fraubaum/VouchGate.git
cd VouchGate
chmod +x infra/deploy.sh
./infra/deploy.sh --project vouchgate-prod \
  --mail-sender guest-access@contoso.com \
  --fallback-group "Guest Access Approvers"

πŸͺŸ Windows (PowerShell)

1. Install prerequisites (once):

  • πŸ”΅ Azure CLI, download and run the MSI installer
  • 🟒 Node.js 24 LTS, download and run the installer (includes npm)
  • πŸ“¦ Static Web Apps CLI:
    npm install -g @azure/static-web-apps-cli

2. Clone and deploy:

git clone https://github.com/daniel-fraubaum/VouchGate.git
cd VouchGate
.\infra\deploy.ps1 -Project vouchgate-prod `
  -MailSender guest-access@contoso.com `
  -FallbackGroup "Guest Access Approvers"

ℹ️ On a first deployment both --mail-sender / -MailSender and --fallback-group / -FallbackGroup are required. The fallback approver group approves requests from users who have no manager, so the portal is not functional for those users without it. A group display name or object ID is accepted; the script resolves and validates it via az ad group show.


The script asks for confirmation, then handles everything end-to-end (~10 minutes):

Step Action
1️⃣ Creates (or reuses) the Entra ID App Registration, sets the identifier URI + access_as_user scope
2️⃣ Deploys all Azure infrastructure via Bicep (single pass)
3️⃣ Assigns User.Invite.All, User.ReadWrite.All, Mail.Send, GroupMember.Read.All to the Managed Identity
4️⃣ Deploys the backend (Azure Functions)
5️⃣ Generates frontend/authConfig.js from deployment outputs
6️⃣ Deploys the frontend (Azure Static Web App)
7️⃣ Registers the Static Web App URL as a redirect URI on the App Registration
8️⃣ Grants admin consent for User.Read

⏳ Allow a few minutes after deployment before the portal is fully functional. Graph API role assignments can take 2-5 minutes to propagate in Entra ID. If you see 403 / permission errors right after deployment, simply wait and refresh.

See docs/deployment.md for re-deploys, partial deploys, custom domains and troubleshooting.


☁️ What gets deployed

Resource Details
πŸ“¦ Resource Group rg-<projectName>
πŸ“Š Log Analytics + Application Insights Telemetry and custom events
πŸ’Ύ Storage Account Runtime storage + VouchGateRequests & VouchGateAudit tables
βš™οΈ App Service Plan Linux B1
🌐 Azure Static Web App Standard tier (SPA frontend)
⚑ Azure Function App Node.js 24, system-assigned Managed Identity
πŸ” Entra ID App Registration MSAL user login + Easy Auth JWT validation

πŸ’° Estimated cost

All prices are approximate, based on West Europe, low/idle usage. Billed in USD by Azure.

Resource Tier ~$/month
βš™οΈ App Service Plan Linux B1 ~$13
🌐 Azure Static Web App Standard ~$9
πŸ’Ύ Storage Account LRS, minimal usage ~$1
πŸ“Š Log Analytics + App Insights Pay-per-use, minimal ingestion ~$1
⚑ Function App Runs on B1 plan (no extra charge) included
πŸ” Entra ID App Registration Free tier included
Total ~$24/month

πŸ”’ Restricting access

⚠️ Security requirement, do this before going live. By default, any user in your Entra ID tenant can log in to the portal. Enable assignment enforcement immediately after deployment.

  1. 🏒 Entra ID β†’ Enterprise Applications β†’ search for your app (e.g. vouchgate-prod-vouchgate-portal)
  2. βš™οΈ Properties β†’ set "Assignment required?" to Yes β†’ Save
  3. πŸ‘₯ Users and groups β†’ Add assignment β†’ select your security group (e.g. SG-VouchGate-Requesters)

Unassigned users receive AADSTS50105 from Entra ID at sign-in, the portal and backend never see the request.

πŸ›‘οΈ Enforcing MFA

⚠️ MFA cannot be enforced from application code. The only secure enforcement is via Entra ID Conditional Access.

Create a Conditional Access policy targeting your app:

  1. Entra ID β†’ Security β†’ Conditional Access β†’ + New policy
  2. Users: your VouchGate security group
  3. Target resources: select your App Registration
  4. Grant: βœ”οΈ Require multi-factor authentication
  5. Enable policy: On β†’ Create

Entra ID then enforces MFA at token issuance, before the portal or backend sees anything.


πŸ“§ Scoping Mail.Send (MANDATORY)

🚨 Mail.Send is an application permission that is tenant-wide by default. Without further restriction, VouchGate's Managed Identity could send email as any mailbox in your tenant. You must restrict it to the sender shared mailbox (MAIL_SENDER) using an Exchange Online Application Access Policy.

Minimal example (full walkthrough in docs/deployment.md):

Connect-ExchangeOnline -Organization contoso.onmicrosoft.com

# Mail-enabled security group containing ONLY the sender mailbox
New-DistributionGroup -Name "VouchGate Mail Senders" -Type Security `
  -PrimarySmtpAddress "vouchgate-mail-senders@contoso.com" `
  -Members "guest-access@contoso.com"

# Restrict the Managed Identity to that group only
New-ApplicationAccessPolicy `
  -AppId "<managed-identity-app-id>" `
  -PolicyScopeGroupId "vouchgate-mail-senders@contoso.com" `
  -AccessRight RestrictAccess `
  -Description "Restrict VouchGate to the guest-access shared mailbox"

# Verify: sender = Granted, any other mailbox = Denied
Test-ApplicationAccessPolicy -Identity "guest-access@contoso.com" -AppId "<managed-identity-app-id>"
Test-ApplicationAccessPolicy -Identity "ceo@contoso.com"          -AppId "<managed-identity-app-id>"

ℹ️ The Application ID is the Managed Identity's service principal appId (named after the Function App), not the portal App Registration. Policy changes can take up to ~30 minutes to take effect.


♻️ Guest lifecycle

VouchGate does not delete or disable guests itself. On approval it stamps each guest with:

  • extensionAttribute13 = approval expiry date (ISO 8601)
  • employeeType = "VouchGate"
  • sponsors = the requester

Expiry enforcement (notify β†’ disable β†’ delete) is handled externally by EntraID Guest User LifeCycle Management, which reads extensionAttribute13 and acts on it. This clean handoff keeps VouchGate focused on the request/approval workflow.

VouchGate never sets extensionAttribute15 = "ExcludeFromLCM", so every guest it provisions remains subject to the external lifecycle cleanup.


🧾 Auditing and logging

VouchGate produces three complementary logging layers.

1. Business audit trail. Every request, approval, denial and escalated decision is written to the VouchGateAudit table in the storage account, capturing the requester, guest, justification, decision, approverType (manager / group / fallback-escalation), timestamps and caller IP. This answers "why does this guest exist and who vouched for them" and is viewable via the Azure Portal or Storage Explorer. Table Storage has no automatic retention: entries are kept until deleted, which is intended for audit purposes. Organizations with retention requirements should export or clean up the table according to their own policy.

2. Technical telemetry. Application Insights captures requests, dependencies (including Microsoft Graph call durations) and custom events, currently VouchGateRequestSubmitted, VouchGateRequestDecided, VouchGateSponsorAssignmentFailed, VouchGateEscalationReminder and VouchGateApproverNotificationFailed. Default retention is 90 days. This layer is for operations and troubleshooting.

3. Entra ID audit log. Guest invitations and user patches performed by the Managed Identity appear in the Entra ID audit log with the Function App service principal as the actor. This shows what VouchGate did at the directory level but not on whose behalf, so cross-reference it with the VouchGateAudit table for the business context: every invitation in the Entra log should correspond to an approved request in VouchGateAudit.

See docs/architecture.md for the audit column reference and an example Application Insights query.


πŸ“ Project structure

β”œβ”€β”€ infra/
β”‚   β”œβ”€β”€ deploy.sh                      ← Full deployment (Bash, macOS/Linux/WSL)
β”‚   β”œβ”€β”€ deploy.ps1                     ← Full deployment (PowerShell, Windows)
β”‚   β”œβ”€β”€ main.bicep                     ← Bicep template (subscription scope)
β”‚   β”œβ”€β”€ bicepconfig.json               ← Bicep linting rules
β”‚   β”œβ”€β”€ main.parameters.example.json   ← Parameters template
β”‚   └── modules/
β”‚       β”œβ”€β”€ monitoring.bicep           ← Log Analytics + Application Insights
β”‚       β”œβ”€β”€ storage.bicep              ← Storage Account + requests & audit tables
β”‚       β”œβ”€β”€ appServicePlan.bicep       ← Linux App Service Plan (B1)
β”‚       β”œβ”€β”€ staticwebapp.bicep         ← Frontend Static Web App
β”‚       └── functionapp.bicep          ← Backend Function App + Easy Auth
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ index.html                     ← Single-page SPA (CSS + JS fully embedded)
β”‚   β”œβ”€β”€ authConfig.example.js          ← Template, copy to authConfig.js and fill in
β”‚   β”œβ”€β”€ staticwebapp.config.json       ← SWA routing + security headers
β”‚   β”œβ”€β”€ logo.svg                       ← App logo (gate + vouch checkmark)
β”‚   β”œβ”€β”€ favicon.svg                    ← Favicon
β”‚   └── css/
β”‚       └── theme.css                  ← Branding overrides (colors, fonts, radii)
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ host.json
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ eslint.config.js               ← ESLint 9 flat config
β”‚   └── src/
β”‚       β”œβ”€β”€ index.js                   ← Entry point (boots telemetry + registers functions)
β”‚       β”œβ”€β”€ functions/
β”‚       β”‚   β”œβ”€β”€ submitRequest.js       ← POST /api/requests
β”‚       β”‚   β”œβ”€β”€ listRequests.js        ← GET  /api/requests?view=mine|pending
β”‚       β”‚   β”œβ”€β”€ myApprover.js          ← GET  /api/my-approver
β”‚       β”‚   β”œβ”€β”€ decideRequest.js       ← POST /api/requests/{id}/decision
β”‚       β”‚   └── escalationReminder.js  ← daily timer (escalation digest)
β”‚       └── lib/
β”‚           β”œβ”€β”€ auth.js                ← Easy Auth + JWKS JWT verification
β”‚           β”œβ”€β”€ graph.js               ← Microsoft Graph (DefaultAzureCredential)
β”‚           β”œβ”€β”€ requests.js            ← VouchGateRequests store (ETag concurrency)
β”‚           β”œβ”€β”€ audit.js               ← VouchGateAudit append-only writer
β”‚           β”œβ”€β”€ approver.js            ← approver resolution + escalation
β”‚           β”œβ”€β”€ telemetry.js           ← Application Insights custom events
β”‚           └── mailTemplates.js       ← Outlook-safe HTML mail templates
└── docs/
    β”œβ”€β”€ architecture.md                ← Architecture overview + auth flow
    β”œβ”€β”€ deployment.md                  ← Detailed deployment guide
    β”œβ”€β”€ CONFIGURATION.md               ← All config values explained
    └── images/                        ← Logo + screenshots

Note on App Registration: Created and configured by the deploy scripts via az ad app CLI, not via a Bicep module.


πŸ“š Documentation


βš–οΈ License

MIT, see LICENSE

About

Self-service guest access request portal for Entra ID with manager approval. Lightweight alternative to Entitlement Management, no Governance licenses required.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages