Building a Secure LAPS Password Portal with Azure and Microsoft Graph

What You Will Build

  • An App registration (backend) to call Microsoft Graph with the correct permissions.
  • A Resource group for all your Azure resources.
  • An Azure Function that queries Microsoft Graph for LAPS information with the App registration.
  • A Log Analytics workspace so every lookup can be tracked by user.
  • An Azure Web App that hosts a simple password and LAPS account lookup interface.
  • An App Service Authentication so only authorized users can access the portal

You need an active Azure subscription with Owner permissions in order to realise the LAPS portal and:

Minimum required:

  • Application Administrator — for creating App Registrations and Enterprise Applications.
  • Cloud Application Administrator — same but without on-premises components.

Additionally required for the Conditional Access policy:

  • Conditional Access Administrator — for creating CA policies.

Or everything in one role:

Global Administrator — has all permissions, easiest but most privileged.


Step 1: Create the App Registration for the Backend

This app registration is used by the Azure Function to call Microsoft Graph.

Go to Entra IDApp registrations → + New registration

  • Name: view laps data
  • Supported account types: Single tenant
  • Redirect URI (optional): leave as is.
  • Click Register

Go to Certificates & secrets+ New client secret → Enter a description: LAPS-data-secret and set the Expires to: Recommended: 180 days → Add

Copy the Value and keep it save you will need it later in step 4.

Go to API permissions → + Add a permissionMicrosoft GraphApplication permissions → Add:

  • Device.Read.All
  • DeviceLocalCredential.Read.All

Remove permissions User.Read. Click Grant admin consent for …

Click Yes.

Step 2: Create the Resource Group

Go to Resource groups → + Create

  • Subscription: select your subscription
  • Name: rg-laps-data-portal (the name you prefer)
  • Region: West Europe (your desired region)
  • Click Review + create → Create

Step 3: Create the Log Analytics Workspace

Go to Log Analytics workspaces → + Create

  • Resource group: rg-laps-data-portal (the RG your create before)
  • Name: law-laps-data-portal (name you prefer)
  • Region: West Europe (your desired region)
  • Click Review + create → Create

Once created, open Azure Cloud Shell

Choose PowerShell when asked.

Run the following script to retrieve the Workspace ID and Primary key:

$law = Get-AzOperationalInsightsWorkspace -ResourceGroupName "rg-laps-data-portal" -Name "law-laps-data-portal"; Write-Host "Workspace ID: $($law.CustomerId)"; Write-Host "Primary Key: $((Get-AzOperationalInsightsWorkspaceSharedKey -ResourceGroupName "rg-laps-data-portal" -Name "law-laps-data-portal").PrimarySharedKey)"

Note both output values — you will need them in Step 4 for the Function App environment variables

Step 4: Create the Azure Function App

Go to Function App → +Create

  • Select hosting option: Consumption (Windows) and Confirm.
  • Resource group: rg-laps-data-portal (from step 2)
  • Function App name: laps-Graph-portal (name you prefer)
  • Runtime stack: PowerShell Core
  • Version: 7.4
  • Region: West Europe (your desired region)
  • Click Review + create → Create

Click: Go to resource.


SettingsEnvironment variablesApp settings+ add:

NameValue
LAPS_TENANT_IDYour tenant ID
LAPS_CLIENT_IDClient ID from step 1
LAPS_CLIENT_SECRETClient secret from Step 1
LAW_WORKSPACE_IDWorkspace ID from Step 3
LAW_PRIMARY_KEYPrimary key from Step 3

After adding the variables click: ApplyConfirm

Go to the Overview → Create in Azure portal.

Select: HTTP trigger → Next.

  • Name: GetLapsPassword
  • Authorization level: Function → Create

In Code + Test → replace the run.ps1 content with the content of the Function script from the GitHub repo and click Save.

Click Get Function URL → copy the URL including the function key, we will need this in step 7.

Step 5: Create the Web App

Go to App ServicesCreate → Web App

  • Resource group: rg-laps-data-portal (from step 2)
  • Name: laps-data-portal (name you prefer)
  • Runtime stack: PHP 8.5
  • OS: Linux
  • Region: West Europe (your desired region)
  • Linux plan: Create new: ASP-LAPS-data-portal → OK
  • Pricing plan: Free F1 (free)
  • Click Review + create → Create

Click go to resource.

Note/copy the URL from Default domain on the Overview page — you need this in the next step.

Step 6: Create the App Registration for the Frontend

This app registration is required before you can enable App Service Authentication in Step 8. You need the Web App URL from Step 5 to complete this step.

Go to Entra IDApp registrations → + New registration

  • Name: LAPS-Portal-frontend (name your prefer)
  • Supported account types: Single tenant only
  • Click Register

Note the Application (client) ID — you will need this in Step 8

Go to Manage → Authentication (Preview)+ Add Redirect URI

Choose Web (not SPA)

  • Enter Redirect URI: https://<default-domein-url-from-step-5>/.auth/login/aad/callback
  • Check ID tokens
  • Click Configure.

Grant admin consent to prevent permission prompts

To ensure no users in your tenant see a permissions consent prompt when opening the portal:

Go to Permissions → click Grant admin consent for <your tenant>

Click Yes.

Remove the permission:

Step 7: Deploy the Frontend

Go to App services → Open the web app your created in step 5 → Development ToolsAdvanced ToolsGo → (opens Kudu)

Open File Manager → navigate to site/wwwroot

Drag and drop index.html and proxy.php from the GitHub repo into the file manager and delete hostingstart.html.

Correct upload:

Wrong upload:

proxy.php acts as a server-side proxy between the browser and the Azure Function. The real Function URL and key are stored as a Web App environment variable (FUNCTION_URL) and never exposed to the browser.

Add the FUNCTION_URL environment variable to the Web App

Go to the Web App (laps-data-portal) → SettingsEnvironment variablesApp settings+ Add:

NameValue
FUNCTION_URLhttps://<your-function-app>.azurewebsites.net/api/GetLapsPassword?code=<your-function-key-from-step-4>

Click ApplyApplyConfirm

You can now test the laps portal by browsing to the Default domain URL from step 4:

Enter an Intune devicename and click Retrieve to check the LAPS data.

Step 8: Enable App Service Authentication on the Web App

Now that the LAPS portal works we want to enable authentication before anyone can access the LAPS portal web app.

Go to the Web App (laps-data-portal) → Settings → AuthenticationAdd identity provider

Choose Microsoft

App registration type: Pick an existing app registration in this directory

Select LAPS-Portal-frontend from Step 6

Client secret expiration: 180 days

Unauthenticated requests: HTTP 302 found redirect:…

Click Add

All you tenant users are now automatically redirected to Microsoft login before they (all tenant users) can access the portal, which is a bit much.

Step 9: Restrict Access to Specific Users

To limit access to the LAPS Portal we can assign access to a specific Entra ID group with (IT support) members.

Go to Entra IDEnterprise ApplicationsLAPS-Portal-frontend (From step 6)

Properties → set Assignment required to YesSave

Users and groupsAdd user/group → assign your admin group

Now users NOT a member of the added group will see this when logging in the LAPS portal web app:

If you want to have a more secure access to the portal go to the optional PIM step.

Optional: Enforce MFA and Session Timeout

To require MFA (Microsoft Authenticator) and automatically sign out users after 60 minutes of inactivity, configure a Conditional Access policy:

Go to Entra IDSecurityProtectConditional Access+ Create New policy

Name: LAPS Portal - Require MFA and Session

Users: select the group that has access to the portal (Step 9)

Target resourcesSelect resources → select LAPS-Portal-frontend

Grant → select Grant access → Require authentication strength → Select Multifactor authentication, click Select.

SessionSign-in frequency → Periodic reauthentication → 1 Hour

Enable policy: On

Click Create.

Now user who sign in the LAPS Portal must complete a MFA step.

View the Audit Log

Every lookup is logged to Log Analytics with the user’s UPN, device name, IP address, and result. To query the log:

  1. Go to your Log Analytics WorkspaceLogs
  2. Run this KQL query:
LAPSPortal_CL
| extend LocalTime = TimeGenerated + 2h
| project LocalTime, CallerUPN_s, DeviceName_s, AccountName_s, CallerIP_s, Result_s
| order by LocalTime desc

+ 2h converts UTC to Central European Summer Time (CEST). Use + 1h in winter (CET).

It can take up to 30 min. after the first retrieve from the portal before the log query returns data.

Save the query via SaveSave as query so you don’t have to retype it. You can also pin the results to your Azure dashboard via Pin toAzure dashboard for quick daily access.

Conclusion

With just a few Azure resources and no custom authentication code, you now have a secure, mobile-friendly LAPS portal that any authorized helpdesk engineer or IT administrator can use from anywhere — whether they’re at their desk or standing next to a device in the field.

No more navigating through the Intune portal to find a LAPS password. Just open the portal, type the device name, and you’re done.

A few things to take away from this build:

  • App Service Authentication secures the portal without writing any authentication code — Azure handles the entire login flow automatically.
  • Store secrets as Function App environment variables, never hardcoded in scripts — this keeps sensitive credentials out of your source code and version control.
  • Every lookup is logged with the caller’s UPN, device name, IP address, and result — giving you full accountability and a clear audit trail of who accessed what and when.
  • Don’t forget to configure Conditional Access with MFA and a session timeout — this adds an important extra layer of security on top of the portal authentication.
  • The first start/sign in to the LAPS portal can take a bit longer. When the Function App has been idle for a while, Azure deallocates the underlying infrastructure to save resources. The first request after a period of inactivity forces Azure to spin up the PowerShell runtime again, which takes 10–30 seconds.

The total Azure cost for normal internal use is effectively zero — the Function App runs on a Consumption plan and the Web App on a free F1 tier.

Automated Deployment via PowerShell Script

Instead of following all the manual steps above, you can deploy the entire LAPS Portal automatically using a single PowerShell script. The script automates every step described in this blog post and has the portal up and running in just a few minutes.

What the script does

The script automatically:

  • Creates the backend App Registration with the correct Graph API permissions and admin consent
  • Creates the Resource Group
  • Creates the Log Analytics Workspace
  • Creates the Azure Function App and deploys the function code
  • Creates the Web App with a unique URL
  • Creates the frontend App Registration with the correct redirect URI
  • Grants admin consent so users never see a permissions prompt
  • Deploys index.html and proxy.php to the Web App
  • Enables App Service Authentication with Microsoft as the identity provider
  • Restricts access to the LAPS-Portal-Admins Entra ID group
  • Creates a Conditional Access policy requiring MFA and a 1-hour session timeout

Requirements

  • An active Azure subscription with Owner permissions
  • One of the following Entra ID roles:
    • Global Administrator (easiest, covers everything), or
    • Application Administrator + Conditional Access Administrator (least privilege)
  • Access to Azure Cloud Shell (portal.azure.com)

How to run

Step 1 — Open Azure Cloud Shell

Go to portal.azure.com and open Cloud Shell. Make sure PowerShell is selected as the shell type.

Step 2 — Sign in to Microsoft Graph

Before running the script, sign in to Microsoft Graph by running the following command in Cloud Shell:

Connect-MgGraph -Scopes "Application.ReadWrite.All","Directory.ReadWrite.All","AppRoleAssignment.ReadWrite.All","Policy.ReadWrite.ConditionalAccess","Policy.Read.All","RoleManagement.ReadWrite.Directory" -UseDeviceAuthentication

You will see a device code and a URL. Open the URL in your browser, enter the code and sign in with your Azure admin account. Consent to the required Graph Command Line Tool permissions.

Step 3 — Run the installation script

Once signed in to Microsoft Graph, run the installation script with this single command:

Invoke-Expression (Invoke-WebRequest -Uri "https://raw.githubusercontent.com/iamsysadmin/LAPS-portal/main/laps_portal_script.ps1" -UseBasicParsing).Content

The script will run through all steps automatically and show the progress in the console. At the end you will see the portal URL.

Step 4 — Add users to the LAPS-Portal-Admins group

After the script completes, go to Entra ID → Groups → LAPS-Portal-Admins and add the helpdesk and IT support users who need access to the portal.

Step 5 — Open the portal

Browse to the URL shown at the end of the script output (first time takes some time). Sign in with your Microsoft account and you are ready to go!

Cleanup script

If you want to remove all resources created by the installation script, run the cleanup script:

Invoke-Expression (Invoke-WebRequest -Uri "https://raw.githubusercontent.com/iamsysadmin/LAPS-portal/main/laps_cleanup_script.ps1" -UseBasicParsing).Content

Type yes to confirm,

The cleanup script removes:

  • The Resource Group and all Azure resources inside it (Function App, Web App, App Service Plan, Log Analytics Workspace, Storage Account)
  • The backend App Registration (View Laps data)
  • The frontend App Registration and Enterprise Application (LAPS-Portal-frontend)
  • The Conditional Access policy

Note: The LAPS-Portal-Admins Entra ID group is not removed by the cleanup script so your group membership is preserved.

Optional: Secure LAPS Portal Access with Privileged Identity Management (PIM)

By default, members of the LAPS-Portal-Admins group have permanent access to the portal. If you want to apply a just-in-time access model (more secure), you can integrate Privileged Identity Management (PIM) so that users must explicitly activate their access before they can retrieve LAPS credentials.

Requirement: This feature requires Microsoft Entra ID P2 licenses, which are included in Microsoft 365 E3/E5 and Microsoft 365 Business Premium.

Why use PIM for the LAPS Portal?

Without PIM, a compromised account that is member of the LAPS-admin group automatically has access to all LAPS passwords at any time.

With PIM:

  • Access is inactive by default — a stolen account cannot access LAPS passwords
  • Access is time-limited — automatically expires after 8 hours
  • Users must provide a justification before access is granted
  • Full audit trail in Entra ID of every activation (who, when, why)
  • Admins receive notifications when access is activated

Enable the LAPS-Portal-Admins group for PIM

Go to Entra ID → Groups → LAPS-Portal-Admins → Properties

Set Microsoft Entra roles can be assigned to the group to Yes (if not allready so) and save.

Note: This setting can only be configured when the group is first created. If you cannot change this, create a new group with this setting enabled and use that group instead.

Go to Entra ID → Identity Governance → Privileged Identity Management → Groups

Click Discover groups and select LAPS-Portal-Admins → Click Manage groups.

Select OK

Go to Entra ID → Identity Governance → Privileged Identity Management → Groups and select: LAPS-Portal-Admins (or the group you used in step 9)

Go to: Manage → Settings and select Member.

Select Edit to change the Role settings to the settings you prefer:

Set the Activation settings to:

Leave the Assignment settings default:

Leave the Notification settings default and select Update.

Create the LAPS-Portal-Eligible group

Instead of adding individual users directly to the PIM group, create a separate group to manage who is eligible. This gives you a clear separation between who can activate access and who currently has active access.

Go to Entra ID → Groups → + New group

  • Group type: Security
  • Group name: LAPS-Portal-Eligible
  • Membership type: Assigned

Click Create.

Add your helpdesk and IT support users as members of this group who need the LAPS portal. This is the only group you need to manage day-to-day — add or remove users here to control who can activate LAPS Portal access.

Assign the LAPS-Portal-Eligible group as eligible member

Instead of assigning individual users, assign the entire LAPS-Portal-Eligible group as an eligible member of the PIM group.

Go to Privileged Identity Management → Groups → LAPS-Portal-Admins → Assignments → Add assignments

  • Memberrole: Member
  • Select member(s): LAPS-Portal-Eligible group
  • Assignment duration: Permanent

Click Next then Assign.

If an admin tries to sign in the LAPS portal without an active PIM role:

When a helpdesk engineer needs to retrieve a LAPS password:

Step 1 — Go PIM

Open http://aka.ms/pimg and sign in with MFA.

Step 2 — Activate the LAPS Portal role

Go to Groups → Find LAPS-Portal-Admins → Click Activate.

Step 3 — Enter a justification, perhaps change the duration if you want.

Click Activate.

Step 4 — Open the LAPS Portal

The role is now active for 8 hours.



Open the LAPS Portal and sign in. After 8 hours the role expires automatically.

Flowchart

So I put all the created components and their relationships into a flowchart.

Click any component to learn more about its role

Browser User opens LAPS Portal login redirect App Registration LAPS-Portal-frontend session token Conditional Access MFA + 60 min timeout Azure Web App index.html + proxy.php 🔒 FUNCTION_URL proxy.php Function App GetLapsPassword 🔒 LAPS_CLIENT_SECRET 🔒 LAW_WORKSPACE_ID · LAW_PRIMARY_KEY App Registration view laps data (backend) client ID + secret Graph API call Microsoft Graph /deviceLocalCredentials audit log Log Analytics law-laps-data-portal Legend Main data flow Auth / supporting 🔒 VAR Encrypted environment variable
🌐
Browser
User opens LAPS Portal
login redirect via LAPS-Portal-frontend
🔐
App Registration – LAPS-Portal-frontend
Frontend / Easy Auth
🛡️
Conditional Access
MFA + 30 min timeout
session token — portal loads
🖥️
Azure Web App
index.html + proxy.php · 🔒 FUNCTION_URL
proxy.php
Function App
🔒 LAPS_CLIENT_SECRET · LAW_WORKSPACE_ID · LAW_PRIMARY_KEY
🔑
App Registration – view laps data
client ID + secret
Graph API call
📊
Microsoft Graph
/deviceLocalCredentials
audit log
📋
Log Analytics
law-laps-data-portal

Theme: Overlay by Kaira