# Entra ID Proxy for OutSystems Developer Cloud

Developing applications with OutSystems Developer Cloud that integrate with the [Microsoft Graph API](https://learn.microsoft.com/en-us/graph/overview) is straightforward if you use **application permissions**. In your action flow, you post your **Application (client) Id**, **Secret**, and required **scopes** to the Token endpoint to get an access token. Then, you **use that access token to interact with Graph API resources**.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Application Permissions -</strong> Allow an app to act <strong>on its own</strong>, without a user. The app gets organization-wide access to resources defined by the permission and requires <strong>admin consent</strong>. This is commonly used for background services or daemons.</div>
</div>

However, in many situations, you want your application to interact with the Graph API **on behalf of the logged-in user**, allowing access only to the resources the user is allowed to use. Additionally, some Graph API resources **are only accessible on behalf of a user**. To access these, you need to perform an OpenID Authorization Code flow, where the user signs in to Entra using the browser to get an access token that represents the user's permissions.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Delegated Permissions -</strong> Allow an app to act <strong>on behalf of a signed-in user</strong>. The app can only access resources that the user has permission to, and it requires user or admin consent. This is commonly used in interactive apps where a user is present.</div>
</div>

In ODC, you can add Microsoft Entra as an Identity Provider, allowing users to sign in to your applications with their Entra account. [You can set it up in just a few minutes by](https://success.outsystems.com/documentation/outsystems_developer_cloud/user_management/configure_authentication_with_external_identity_providers/):

* Registering and configuring an application in your Microsoft Entra tenant.
    
* Configuring a new Identity Provider in the ODC Portal using your registration details.
    
* Enabling the Identity Provider in your applications as an additional or sole authentication method.
    

But unfortunately, ODC does not let you retrieve the **original Microsoft Entra access token** needed to access Graph API resources. Instead, ODC uses the **identity token** from Microsoft Entra to map (or create) a user in your ODC environment. The original identity and access tokens retrieved from Entra are not kept.

One option is to start another **Authorization Code flow** in your application to request an access token. However, this isn't a great user experience. The user already signs in with their Entra credentials and then has to authenticate again just for you to get the access token.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">If you're interested in developing your own Authorization Code flow in OutSystems, please watch my webinar recording <a target="_self" rel="noopener noreferrer nofollow" href="https://youtu.be/2cSsg5ws1H4" style="pointer-events: none">Microsoft Graph API with OutSystems Delegated Permissions</a>. If your end-users sign in with an Identity Provider other than Entra, you will also find my article <a target="_self" rel="noopener noreferrer nofollow" href="https://itnext.io/acquire-and-link-multiple-oauth-tokens-to-outsystems-users-for-delegated-access-b2ba74ca78a0" style="pointer-events: none">Acquire and Link multiple OAuth Tokens to OutSystems users for delegated access | ITNEXT</a> helpful.</div>
</div>

The other option, which is the focus of this article, is to implement an **Entra ID proxy**. This proxy acts as an Identity Provider for your applications, forwards requests to Microsoft Entra, and **caches the tokens** retrieved from Microsoft Entra for you to use.

**Entry ID Proxy** is available on **OutSystems Forge** for OutSystems Developer Cloud.

# Entra ID Proxy Overview

Entra ID Proxy is an OutSystems application that needs to be set up as an **Identity Provider** in the ODC Portal. It provides three REST API Endpoints:

* **openid-configuration** - This retrieves the original OpenID discovery document from your registered Entra application and changes the authorize and token endpoint values to point to the Proxy instead of the Microsoft Entra endpoints.
    
* **authorize** - This proxies authorize requests and redirects the user's browser to the Microsoft Entra authorize endpoint.
    
* **token** - This endpoint not only proxies requests to the Entra token endpoint but also caches the identity and access tokens from Microsoft Entra after the authorization code exchange.
    

Let's take a high-level look at how the **Entra ID Proxy** interacts with an OutSystems application, the platform's Identity Management, and Microsoft Entra.

![Entra ID Proxy Sequence Diagram](https://cdn.hashnode.com/res/hashnode/image/upload/v1763652083075/388942e4-af9d-4652-88f0-972f647bf95e.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>ODC Identity Broker</strong> is the Identity Management component of OutSystems Developer Cloud. It manages Identity Providers and handles the entire authentication process for the platform and the applications you create.</div>
</div>

# Prerequisites

After downloading the Entra ID Proxy application, you first need to register an application in your Entra tenant, as explained in the documentation: [Add Microsoft Entra ID for use as an external identity provider - ODC Documentation](https://success.outsystems.com/documentation/outsystems_developer_cloud/user_management/configure_authentication_with_external_identity_providers/add_microsoft_entra_id_for_use_as_external_identity_provider/).

## Entra ID Application

Ensure you have assigned the following delegated permissions to your application registration:

* **openid** - This scope allows OutSystems to retrieve the identity token used to map or create a user.
    
* **email** - Enables the **email** and **email\_verified** claim in the identity token.
    
* **profile** - Adds general profile information, such as the name, to the identity token.
    
* **offline\_access** - This scope lets the proxy request a refresh token along with the identity and access token. The Proxy application needs this token to automatically refresh the access token after it expires, without requiring the user to log in again.
    

Additionally, add any extra Graph API permissions, such as *Mail.Read* and *Mail.Send*, for the resources you want to use. Your API permission screen should look like this:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763716656417/4ccbfb9b-ee2e-4bf0-89d2-1e84b4820540.png align="center")

From your Entra application you need the following informations copied for the next steps

* Application (client) Id
    
* Client Secret
    
* Directory (tenant) Id
    
* API / Permissions name of the additional delegated permissions you added
    

## Entra ID Proxy Settings

Next, we will configure the Entra ID Proxy application settings. In the ODC Portal, select the Entra ID Proxy application and set up the following:

* **EntraTenantId** - Directory (tenant) Id of your registered Entra application
    
* **EntraScope** - Space-delimited Graph API Scopes (**not** openid, email, profile, and offline\_access)
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Unfortunately, at the time of writing this article, OutSystems does not accept <strong>scope names with a dot</strong> in them. Therefore, it is necessary to configure the Graph scopes here, and Entra ID Proxy will add them during the Authorization Code flow.</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763718043793/bf7cb7f6-5d22-44b2-a735-3eb0943a07cb.png align="center")

## Identity Provider

Finally, we configure **Entra ID Proxy** as an Identity Provider.

For the Discovery endpoint, instead of using Microsoft Entra's discovery endpoint directly, you configure the **Entra ID Proxy** Discovery endpoint. The URL looks like this:

`https://<FQN of your stage>/EntraIDProxy/rest/OAuth2/well-known/openid-configuration`

* **Client ID** - Paste the Application (client) Id value from the Entra application registration.
    
* **Client Secret** - Paste the generated client secret value from the Entra application registration.
    
* **Scope Mapping** - The scopes **openid**, **email**, and **profile** are already configured by default. Add another entry: **offline\_access**.
    

Leave all other values as they are and save the configuration. Then, assign the new Identity Provider to the stage and applications.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Remember to add the generated <em>Redirect URI</em> and <em>Logout URL</em> to your Entra application registration as explained in <a target="_self" rel="noopener noreferrer nofollow" href="https://success.outsystems.com/documentation/outsystems_developer_cloud/user_management/configure_authentication_with_external_identity_providers/add_microsoft_entra_id_for_use_as_external_identity_provider/" style="pointer-events: none">Add Microsoft Entra ID for use as an external identity provider - ODC Documentation</a>.</div>
</div>

With all the prerequisites completed, you should now be able to sign in to applications using the newly configured Identity Provider.

# Retrieving an Access Token

Entra ID Proxy provides two **service actions** that let you retrieve an access token:

* **Entra\_GetAccessTokenByUpn** - This returns an access token for a given Universal Principal Name, typically the user's email address.
    
* **Entra\_GetAccessTokenByObjectId** - This returns an access token for a given Object Identifier, which is the unique identifier of a user object in Entra.
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">You might consider mapping the Object Identifier (<strong>oid</strong>) from Entra to the username in OutSystems Developer Cloud and, instead of mapping the email claim, map the Universal Principal Name (<strong>upn</strong>) to the email.</div>
</div>

## Implementation

Both service actions call the server action `GetAccessTokenByUpnOrObjectId`.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763878611710/bfef0894-0666-4c01-ad90-8d001bc907ea.png align="center")

This action performs the following steps:

* Attempts to retrieve the cache entry from the Token entry using either the Universal Principal Name or Object Identifier. If no entry exists, the action returns with `IsSuccess = False`.
    
* Deserializes the binary payload, which contains the access token and refresh token, of the found entry.
    
* Depending on whether the access token is still valid or has expired, it either returns the access token directly from the cache or tries to refresh the token before returning it.
    

## Retrieving an Access Token in your Applications

In your application create a wrapper server action `GetAccessToken` that performs the following steps.

* Lookup the user record from the User entity filtered to GetUserId().
    
* Call the **Entra\_GetAccessTokenByUpn** service action
    
* Return the success indicator **IsSuccess** and the access token
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763879527200/f11eb8ae-16d4-4945-a5bf-11d149feb59c.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">You should pass the <strong>IsSuccess </strong>indicator up to your frontend application. If it is False, trigger a Security Exception to start a new user sign-in flow.</div>
</div>

# Remarks on the Token Store

The **Token** entity in **Entra ID Proxy** stores the following information

* **Audience** - Extracted from aud claim of the access token.
    
* **Issuer** - Extracted from the iss claim of the access token. This identifies your application registration.
    
* **IssuedOn** - Extracted from the iat claim and specifies when the access token was issued.
    
* **ExpiresOn** - Extracted from the exp claim and specifies when the access token expires. By default Entra access tokens expire after 1 hour.
    
* **Subject** - Extracted from the sub claim. Unique identifier of the user or principal in Entra. Typically the same value as ObjectId, but not guaranteed.
    
* **ObjectId** - Extracted from the oid claim. Unique Identifier of the user or principal in Entra. Use this instead of Subject.
    
* **Payload** - Access and Refresh token as binary data.
    
* **ClientId** - Application (client) Id for your application registration.
    
* **ClientSecret** - Client Secret of your application registration.
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Important</strong>: The version of <strong>Entra ID Proxy</strong> available on the Forge stores some sensitive data unencrypted in the <strong>Token </strong>entity, including the <strong>Client Secret</strong> and the <strong>access and refresh token</strong> values. In a production environment, I strongly recommend using either <a target="_self" rel="noopener noreferrer nofollow" href="https://without.systems/application-level-encryption-in-outsystems" style="pointer-events: none">application-level encryption</a> or moving this sensitive data to a secrets management system like <a target="_self" rel="noopener noreferrer nofollow" href="https://developer.hashicorp.com/vault" style="pointer-events: none">HashiCorp Vault</a>. I am using the latter.</div>
</div>

# Summary

In this article, I'm introducing my OutSystems Forge component, **Entra ID Proxy**. **Entra ID Proxy** acts as an Identity Provider proxy in the OutSystems Developer Cloud and stores access tokens retrieved from Microsoft Entra after user authentication. These access tokens can be used to interact with the Microsoft Graph API with delegated permissions, allowing your application to interact with the Graph API on behalf of a user and their permissions. The access token can also be used to get more user information through the user info endpoint or the User resource in the Graph API.

Thank you for reading. I hope you enjoyed it and that I've explained the important parts clearly. If not, please let me know 😊 Your feedback is greatly appreciated.

Follow me on LinkedIn to receive notifications whenever I publish something new.
