Azure Proxy — App Setup

Create the Entra ID app registration and Application Proxy configuration used to publish the Peek backend. Both methods below produce the same result; use whichever is more convenient.

Prerequisites

  • Entra tenant with a Global Administrator account.

  • A licence that includes Application Proxy (Entra ID P1 or P2).

  • A connector group already created and at least one connector registered — see Azure Proxy — Connector VM.

  • Internal URL resolvable from the connector VM. Application Proxy rejects raw IPs for the internal URL, so use a hostname (a hosts file entry on the connector is acceptable).

Naming conventions used below:

Placeholder

Example / meaning

<tenant-id>

253aed61-d766-447d-8c13-44d7e0daeeb9

<app-name>

PeekFieldApp

<internal-url>

https://peek-internal.example.com

<external-url>

https://peekfieldapp-synerty.msappproxy.net/

<bundle-id>

com.synerty.peek

<connector-group>

PeekConnectorGroup

Option A — PowerShell (Microsoft Graph)

Run from any machine with PowerShell 7+ and network access to Graph.

Install the Graph module (once):

Install-Module Microsoft.Graph -Scope CurrentUser

Authenticate:

Connect-MgGraph -Scopes `
    "Application.ReadWrite.All", `
    "Directory.ReadWrite.All", `
    "DelegatedPermissionGrant.ReadWrite.All"

Create the app registration:

$app = New-MgApplication -DisplayName "PeekFieldApp" `
    -SignInAudience "AzureADMyOrg"

$appId      = $app.AppId
$objectId   = $app.Id
$externalUrl = "https://peekfieldapp-synerty.msappproxy.net/"

Add the iOS platform redirect URI:

Update-MgApplication -ApplicationId $objectId -PublicClient @{
    RedirectUris = @("msauth.com.synerty.peek://auth")
}

Register the Application Proxy external URL as a Web redirect URI. Application Proxy’s pre-authentication flow uses this same app registration and sends redirect_uri=<external-url> to Entra — without it Entra rejects the login with AADSTS50011: The redirect URI ... does not match the redirect URIs configured for the application:

Update-MgApplication -ApplicationId $objectId -Web @{
    RedirectUris = @($externalUrl)
}

Warning

Register the external URL under Web, not Single-page application (SPA). Application Proxy pre-auth is a confidential-client server-side token exchange with no PKCE; SPA registration forces Entra to require PKCE and login fails with AADSTS9002325: Proof Key for Code Exchange is required for cross-origin authorization code redemption.

Expose the user_impersonation API scope and set token version to v2. Use the Application Proxy external URL as the identifier URI — this keeps the scope human-readable (https://<external-url>/user_impersonation) and is consistent across all Peek proxy deployments:

$scopeId = [guid]::NewGuid().ToString()
Update-MgApplication -ApplicationId $objectId `
    -IdentifierUris @($externalUrl.TrimEnd('/')) `
    -Api @{
        RequestedAccessTokenVersion = 2
        Oauth2PermissionScopes = @(@{
            Id                      = $scopeId
            AdminConsentDescription = "Access Peek as the signed in user"
            AdminConsentDisplayName = "Access Peek"
            IsEnabled               = $true
            Type                    = "User"
            Value                   = "user_impersonation"
        })
    }

Note

RequestedAccessTokenVersion = 2 is required. The Peek field app uses the Entra v2.0 token endpoint (/oauth2/v2.0/token); leaving this at the default (null = v1) causes token acquisition to fail silently after the redirect returns from login.microsoftonline.com.

Create the service principal and grant admin consent:

$sp = New-MgServicePrincipal -AppId $appId

# Allow any member of the tenant to use the app
Update-MgServicePrincipal -ServicePrincipalId $sp.Id `
    -AppRoleAssignmentRequired:$false

Enable Application Proxy (Graph beta endpoint — not in v1.0):

$patch = @{
    onPremisesPublishing = @{
        internalUrl                 = "https://peek-internal.example.com"
        externalUrl                 = "https://peekfieldapp-synerty.msappproxy.net/"
        externalAuthenticationType  = "aadPreAuthentication"
        applicationServerTimeout    = "Long"
        isTranslateHostHeaderEnabled = $true
        isHttpOnlyCookieEnabled     = $true
        isSecureCookieEnabled       = $true
    }
}
Invoke-MgGraphRequest -Method PATCH `
    -Uri "https://graph.microsoft.com/beta/applications/$objectId" `
    -Body ($patch | ConvertTo-Json -Depth 5)

Assign the connector group to the app:

$groupId = (Invoke-MgGraphRequest -Method GET `
    -Uri "https://graph.microsoft.com/beta/onPremisesPublishingProfiles/applicationproxy/connectorGroups" `
    ).value `
    | Where-Object { $_.name -eq "PeekConnectorGroup" } `
    | Select-Object -ExpandProperty id

Invoke-MgGraphRequest -Method PUT `
    -Uri "https://graph.microsoft.com/beta/applications/$objectId/connectorGroup/`$ref" `
    -Body (@{ "@odata.id" = `
        "https://graph.microsoft.com/beta/onPremisesPublishingProfiles/applicationproxy/connectorGroups/$groupId" `
    } | ConvertTo-Json)

Note

applicationServerTimeout = Long is required for WebSocket support. The default (Default, 85s) will cut long-lived connections.

Option B — Entra Admin Portal (UI)

  1. Sign in to https://entra.microsoft.com as a Global Administrator.

  2. Register the application

    • Go to Applications -> App registrations -> + New registration.

    • Name: PeekFieldApp.

    • Supported account types: Accounts in this organizational directory only.

    • Redirect URI: leave blank (added per platform below).

    • Click Register, then note the Application (client) ID.

  3. Add platforms

    Go to Authentication -> + Add a platform.

    • iOS / macOS: Bundle ID com.synerty.peek. Entra auto-generates the redirect URI msauth.com.synerty.peek://auth.

    • Web: add the Application Proxy external URL, e.g. https://peekfieldapp-synerty.msappproxy.net/. Application Proxy pre-authentication uses this same app registration and sends the external URL as its redirect URI, so it must be registered here or the proxy login fails with AADSTS50011 (redirect URI mismatch).

    Do not add the external URL under Single-page application — that forces PKCE on the proxy’s server-side token exchange and login fails with AADSTS9002325.

  4. Expose an API

    1. Go to Expose an API and click Set next to Application ID URI.

    2. Replace the default api://<client-id> with the Application Proxy external URL (without the trailing slash), e.g. https://peekfieldapp-synerty.msappproxy.net. Using the proxy URL keeps the OAuth2 scope human-readable and consistent across deployments.

    3. Click + Add a scope.

    4. Set the scope name to user_impersonation.

    5. Set Who can consent to Admins and users.

    6. Click Add scope.

    7. Go to Manifest.

    8. Set "requestedAccessTokenVersion": 2 and save. The Peek field app uses the v2.0 token endpoint; without this the token exchange fails silently after the OAuth2 redirect.

  5. Application Proxy

    • Go to Applications -> Enterprise applications -> + New application -> On-premises application.

    • Name: PeekFieldApp.

    • Internal URL: https://peek-internal.example.com (must be a hostname — raw IPs are rejected; see Azure Proxy — Connector VM for the hosts file workaround).

    • External URL: Let Azure generate one — produces https://peekfieldapp-synerty.msappproxy.net/.

    • Pre-Authentication: Microsoft Entra ID.

    • Connector Group: PeekConnectorGroup.

    • Additional settings: set Backend Application Timeout to Long (required for WebSocket).

    • Click Add.

  6. User assignment

    • Open the new enterprise application -> Properties.

    • Assignment required? -> No (so anyone in the tenant can use it).

    • Save.

Verification

The external URL should now return a 302 to https://login.microsoftonline.com with client_id matching your app:

curl -sI https://peekfieldapp-synerty.msappproxy.net/ | head

Open the external URL in a browser to confirm that sign-in succeeds and the Peek “Enroll This Device” page loads.

Troubleshooting

AADSTS50011: The redirect URI … does not match the redirect URIs configured for the application. The Application Proxy external URL has not been registered. Add it under the Web platform of the app registration (see Step 3 above).

AADSTS9002325: Proof Key for Code Exchange is required for cross-origin authorization code redemption. The Application Proxy external URL has been registered under the Single-page application (SPA) platform. Move it to Web — the proxy’s pre-auth token exchange does not use PKCE. Verify with:

# Graph, expects spa.redirectUris == []
#         and  web.redirectUris contains the external URL
curl -H "Authorization: Bearer <token>" \
    "https://graph.microsoft.com/v1.0/applications/<objectId>?\$select=spa,web"

OAuth2 login completes (redirect returns) but the app stays on the loading screen / no token is stored. requestedAccessTokenVersion on the app registration is null or 1. The Peek field app uses the v2.0 token endpoint; requesting a v1 token via that endpoint fails silently. Set it to 2 in Manifest or via Graph:

PATCH https://graph.microsoft.com/v1.0/applications/<objectId>
{ "api": { "requestedAccessTokenVersion": 2 } }

Forbidden / “You are not authorized to access this application”. Either (a) the user is not a member of the tenant / not assigned, and the Enterprise Application has Assignment required = Yes, or (b) the AADSTS9002325 failure above caused the proxy to fall back to the Forbidden page. Clear SPA redirect URIs first, then if the error remains, set Assignment required to No or add the user.

Reference Configurations

The non-default fields for a Peek Application Proxy app registration are shown below. Fields set to null, [], or boilerplate defaults are omitted. Replace <external-url> and <bundle-id> with your values.

To download the full JSON for any registration:

# Microsoft Graph (requires Directory.Read.All or equivalent)
GET https://graph.microsoft.com/v1.0/applications/<objectId>
Authorization: Bearer <token>

# Azure CLI
az ad app show --id <appId>

# PowerShell
Get-MgApplication -ApplicationId <objectId> | ConvertTo-Json -Depth 10
{
  "displayName": "<app-name>",
  "appId": "<application-client-id>",
  "id": "<object-id>",
  "signInAudience": "AzureADMyOrg",
  "identifierUris": ["https://<external-url>"],
  "publicClient": {
    "redirectUris": ["msauth.<bundle-id>://auth"]
  },
  "web": {
    "homePageUrl": "https://<external-url>/",
    "logoutUrl":   "https://<external-url>/?appproxy=logout",
    "redirectUris": ["https://<external-url>/"],
    "implicitGrantSettings": {
      "enableAccessTokenIssuance": false,
      "enableIdTokenIssuance": true
    }
  },
  "spa": { "redirectUris": [] },
  "api": {
    "requestedAccessTokenVersion": 2,
    "oauth2PermissionScopes": [
      { "value": "user_impersonation", "type": "User", "isEnabled": true }
    ]
  },
  ...
}