VouchGate is configured entirely through Function App application settings.
The deploy scripts (infra/deploy.sh / infra/deploy.ps1) provision these from
infra/modules/functionapp.bicep, and the frontend reads a small subset from a
generated frontend/authConfig.js. For local development, copy
backend/local.settings.json.example → backend/local.settings.json.
| Setting | Purpose | Default |
|---|---|---|
TENANT_ID |
Entra ID tenant ID | required |
AUTH_CLIENT_ID |
App Registration client ID (Easy Auth / JWT audience) | required |
TABLE_STORAGE_CONNECTION_STRING |
Storage account holding both tables | required |
REQUESTS_TABLE_NAME |
Requests table name | VouchGateRequests |
AUDIT_TABLE_NAME |
Audit table name | VouchGateAudit |
GRAPH_API_ENDPOINT |
Microsoft Graph endpoint | https://graph.microsoft.com |
JUSTIFICATION_MIN_LENGTH |
Minimum justification length (chars) | 10 |
MAX_DURATION_DAYS |
Maximum requestable access duration (days) | 365 |
ESCALATION_DAYS |
Days a request may stay pending before fallback group members may also approve it | 5 |
ESCALATION_REMINDER_CRON |
NCRONTAB schedule for the daily escalation reminder timer | 0 0 7 * * * |
APPROVER_FALLBACK_GROUP_ID |
Object ID of the fallback approver group. Required so users without a manager have an approver (also used for escalation). Set at deploy time from --fallback-group / -FallbackGroup. |
required |
ALLOWED_GUEST_DOMAINS |
Comma-separated allow-list of guest email domains (empty = allow all) | (empty) |
BLOCKED_GUEST_DOMAINS |
Comma-separated block-list of guest email domains (empty = block none) | (empty) |
MAIL_SENDER |
Shared mailbox used as the sender for all notification mails | required |
PORTAL_URL |
Public URL of the portal, used for mail deep links (/?request={id}) and the invitation redirect |
Static Web App URL |
The following runtime settings are also present (managed by the platform /
deploy): AzureWebJobsStorage, FUNCTIONS_WORKER_RUNTIME,
FUNCTIONS_EXTENSION_VERSION, WEBSITE_NODE_DEFAULT_VERSION,
APPLICATIONINSIGHTS_CONNECTION_STRING, AZURE_LOG_LEVEL.
Generated by the deploy scripts from deployment outputs and read by the SPA as
window.VOUCHGATE_CONFIG:
| Key | Purpose |
|---|---|
msalClientId |
App Registration client ID |
tenantId |
Entra ID tenant ID |
apiBaseUrl |
Backend Function App base URL (no trailing slash) |
apiScope |
api://{clientId}/access_as_user |
justificationMinLength |
Mirrors the backend minimum (client-side hint only) |
durationOptions |
Selectable durations, e.g. [30, 90, 180, 365] |
defaultDuration |
Pre-selected duration in days |
authConfig.jsis gitignored, it is regenerated on every deploy and must never be committed with real tenant/client IDs.
A request is normally decided by the requester's manager (resolved via Graph
/users/{id}/manager), or, when the requester has no manager, by a member of
the fallback approver group (APPROVER_FALLBACK_GROUP_ID).
If a request stays pending longer than ESCALATION_DAYS, members of the
fallback approver group become additional valid approvers for it (including
for manager-routed requests). This is enforced server-side in both the pending
list (GET /api/requests?view=pending) and the decision endpoint
(POST /api/requests/{id}/decision); eligibility is age-based and re-checked via
Microsoft Graph on every call.
A daily timer function (escalationReminder, scheduled by
ESCALATION_REMINDER_CRON) sends a single digest mail to the fallback group
listing all pending requests past ESCALATION_DAYS that have not been escalated
yet, then stamps escalatedAt on those requests so the reminder is not sent
again. The mail is only stamped after a successful send, so a send failure is
retried on the next run.
Decisions made through escalation are audited with
approverType = fallback-escalation (versus manager or group).
ALLOWED_GUEST_DOMAINS and BLOCKED_GUEST_DOMAINS are comma-separated domain
lists evaluated at request submission:
- If the guest's email domain is in
BLOCKED_GUEST_DOMAINS, the request is rejected (DOMAIN_BLOCKED). - If
ALLOWED_GUEST_DOMAINSis non-empty and the domain is not in it, the request is rejected (DOMAIN_NOT_ALLOWED). - If
ALLOWED_GUEST_DOMAINSis empty, all domains are allowed (subject to the block-list).
Example: ALLOWED_GUEST_DOMAINS = "partner.com,contractor.io".
Assigned by the deploy scripts to the Function App's system-assigned Managed Identity (application permissions, admin-consented):
| Permission | Why VouchGate needs it |
|---|---|
User.Invite.All |
Create the guest via POST /invitations |
User.ReadWrite.All |
Resolve the requester's manager; patch the guest (extensionAttribute13, employeeType, sponsors) |
Mail.Send |
Send notification mails as MAIL_SENDER |
GroupMember.Read.All |
Verify fallback approver group membership (checkMemberGroups) and resolve group member mailboxes for BCC notifications |
The App Registration additionally has the delegated User.Read permission
(admin-consented) for interactive sign-in.
Mail.Send as an application permission is tenant-wide by default: the
Managed Identity could send email as any mailbox. It must be restricted to
the MAIL_SENDER mailbox using an Exchange Online Application Access Policy.
See docs/deployment.md
for the full Connect-ExchangeOnline / New-ApplicationAccessPolicy /
Test-ApplicationAccessPolicy walkthrough.
By default every user in the tenant can sign in. Restrict access after deploy:
- Entra ID → Enterprise Applications → the VouchGate portal app.
- Properties → Assignment required? → Yes → Save.
- Users and groups → assign a dedicated security group
(e.g.
SG-VouchGate-Requesters).
Enforce MFA with a Conditional Access policy targeting the same group. See docs/deployment.md.
On approval VouchGate stamps the approval expiry on the guest as
extensionAttribute13 (ISO 8601) and sets employeeType = "VouchGate" and
sponsors = requester. Expiry cleanup is handled externally by
EntraID Guest User LifeCycle Management,
which reads extensionAttribute13.
VouchGate never sets
extensionAttribute15 = "ExcludeFromLCM", so every guest it provisions remains subject to the external lifecycle cleanup.