.. _azure_proxy_setup: ========================== 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 :ref:`azure_proxy_windows_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 | +========================+===================================================+ | ```` | ``253aed61-d766-447d-8c13-44d7e0daeeb9`` | +------------------------+---------------------------------------------------+ | ```` | ``PeekFieldApp`` | +------------------------+---------------------------------------------------+ | ```` | ``https://peek-internal.example.com`` | +------------------------+---------------------------------------------------+ | ```` | ``https://peekfieldapp-synerty.msappproxy.net/`` | +------------------------+---------------------------------------------------+ | ```` | ``com.synerty.peek`` | +------------------------+---------------------------------------------------+ | ```` | ``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=`` 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:///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** #. Go to **Expose an API** and click **Set** next to **Application ID URI**. #. Replace the default ``api://`` 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. #. Click **+ Add a scope**. #. Set the scope name to ``user_impersonation``. #. Set **Who can consent** to *Admins and users*. #. Click **Add scope**. #. Go to **Manifest**. #. 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 :ref:`azure_proxy_windows_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 " \ "https://graph.microsoft.com/v1.0/applications/?\$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/ { "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 ```` and ```` 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/ Authorization: Bearer # Azure CLI az ad app show --id # PowerShell Get-MgApplication -ApplicationId | ConvertTo-Json -Depth 10 .. code-block:: json { "displayName": "", "appId": "", "id": "", "signInAudience": "AzureADMyOrg", "identifierUris": ["https://"], "publicClient": { "redirectUris": ["msauth.://auth"] }, "web": { "homePageUrl": "https:///", "logoutUrl": "https:///?appproxy=logout", "redirectUris": ["https:///"], "implicitGrantSettings": { "enableAccessTokenIssuance": false, "enableIdTokenIssuance": true } }, "spa": { "redirectUris": [] }, "api": { "requestedAccessTokenVersion": 2, "oauth2PermissionScopes": [ { "value": "user_impersonation", "type": "User", "isEnabled": true } ] }, ... }