Protecting Entra ID SSO with SGNL

Introduction

Protected Systems are applications, services, or infrastructure that you want to protect with SGNL. In this guide, we’ll describe how to achieve fine-grained access control at sign-in time with Entra ID and SGNL, enabling the right amount of access to applications and infrastructure connected to Entra ID.

With this integration, Entra ID need not know about the policies, systems of record, or any of the data in SGNL - it simply needs to pass to SGNL:

  • Who/What is requesting the access (The Principal)
  • (Optional) What is attempting to be accessed (The Asset)
  • (Optional) What operation is being attempted on the asset (The Action)
  • An access token that ensures the Protected System is a legitimate caller into SGNL

Prerequisites

  • An Entra ID Tenant
  • A SGNL Client
  • A SGNL User Account with Admin privileges
  • At least 1 Entra ID system of record integrated to SGNL, containing principals and (optionally) assets that will be evaluated in access evaluations
  • (Optional) 1 or more policies that you want assigned to the integration

Creating a Protected System in SGNL

  1. Log-In to your SGNL Client with an Admin Account
  2. From the left navigation pane, select Protected Systems and Add, or simply click Add from the SGNL Dashboard
  3. Select ‘Entra ID’ from the Identity Providers category in the list of integrations
  4. Give your integration a descriptive display name and description
  5. Specify the Default Policy to be applied to your App
    • Allow: If no policies provide a decision for an access request, SGNL will respond to the access request with an Allow decision
    • Deny: If no policies provide a decision for an access request, SGNL will respond to the access request with a Deny decision
  6. Next, you’ll need to configure which identifier Entra ID is using to describe your user/principal
    • This is likely your Entra ID user’s email address or login name. This should be in the format of the Principal ID of the user that will request access to the Protected System.
    • e.g. If an Entra ID user will be requesting access to this Protected System with the Principal ID as their login name, the principal identifier should be the Entra ID principal name.
  7. You’ll also need to define the identifiers for the types of Assets that you are looking to protect
    • This might be app identifiers sourced directly from Entra ID, or something more granular within one of your other Systems of Record
  8. Once configured, click Continue to save your configuration and move on to other configuration steps

Configuring Authentication

  1. Authentication ensures that only authorized systems can make requests into SGNL, as well as verifying the identity of an integration in order to effectively evaluate Policies - to access Authentication settings, open your AWS protected system and select the Authentication tab

    SGNL - Authentication

  2. Click Generate Token

  3. Give your token a descriptive name so that you know how it’s being used in the future and click to Generate Token

    SGNL - Generate Token

  4. On the next screen, copy the token - this will be used by Entra ID to make access requests to SGNL using the SGNL Access Service API

    Note: The value of this token is not available again after this screen, so ensure you securely store it for steps later in this guide

    SGNL - Token

Integrating Entra ID Custom Authentication Extensions with SGNL

Integrating SGNL with Entra ID, for the purposes of securing SSO, relies on Entra ID Custom Authentication Extensions. Entra ID Custom Authentication Extensions enable Entra ID to make an outbound request that can be routed to SGNL, in order to determine whether a user should have access to a specific application at that point in time, or what roles, claims, or tags to include in their token.

The steps to configure SGNL with Entra ID SSO are fairly straightforward. Microsoft includes a great guide for configuring Custom Authentication Extensions. You’ll need a custom claim provider API endpoint to get started, the good news is that this can be easily deployed with our out of box connector, or code that you create inside of Azure Functions or Container Apps - a sample is included below:

#r "Newtonsoft.Json"
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Text;

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);

    // Extract the relevant query fields from the request body
    string principalId = data?.data.authenticationContext.user.userPrincipalName;
    string ipAddress = data?.data.authenticationContext.client.ip;
    string assetId = data?.data.authenticationContext.clientServicePrincipal.appDisplayName;

    // Read the correlation ID from the Entra ID  request    
    string correlationId = data?.data.authenticationContext.correlationId;

    // Optional claims to return to Entra ID
    ResponseContent r = new ResponseContent();
    r.data.actions[0].claims.CorrelationId = correlationId;
    r.data.actions[0].claims.ApiVersion = "1.0.0";
    r.data.actions[0].claims.DateOfBirth = "05/08/1984";
    r.data.actions[0].claims.CustomRoles.Add("Reader");
    r.data.actions[0].claims.CustomRoles.Add("Admin");

    // Create a request for the SGNL Access API 
    WebRequest request = WebRequest.Create("https://access.sgnlapis.cloud/access/v1/evaluations");

    request.Method = "POST";
    // TODO: Do this elegantly…
    string postData = "{\"principal\":{\"id\":\"" + principalId + "\",\"ipAddress\":\"" + ipAddress  + "\"},\"queries\":[{\"action\":\"access\",\"assetId\":\"" + assetId + "\"}]}"
    byte[] byteArray = Encoding.UTF8.GetBytes(postData);
    // Set the ContentType property of the WebRequest.
    request.ContentType = "application/json";
    request.Headers["Authorization"] = "Bearer eyJk..5MzkwIn0=";
    // Get the request stream
    Stream dataStream = request.GetRequestStream();
    dataStream.Write(byteArray, 0, byteArray.Length);
    dataStream.Close();
    // Get the response
    WebResponse response = request.GetResponse();
    dataStream = response.GetResponseStream();
    StreamReader reader = new StreamReader(dataStream);
    // Read the content
    string responseFromServer = reader.ReadToEnd();
    dynamic responseData = JsonConvert.DeserializeObject(responseFromServer);
    string decision = responseData?.decisions[0].decision;
    reader.Close();
    dataStream.Close();
    response.Close();
    // Return a decision to the app
    if (decision == "Allow") {
        return new OkObjectResult(r);
    }
    else {
        return new BadRequestObjectResult("Access was Denied :-(");
    }
}

public class ResponseContent{
    [JsonProperty("data")]
    public Data data { get; set; }
    public ResponseContent()
    {
        data = new Data();
    }
}

public class Data{
    [JsonProperty("@odata.type")]
    public string odatatype { get; set; }
    public List<Action> actions { get; set; }
    public Data()
    {
        odatatype = "microsoft.graph.onTokenIssuanceStartResponseData";
        actions = new List<Action>();
        actions.Add(new Action());
    }
}

public class Action{
    [JsonProperty("@odata.type")]
    public string odatatype { get; set; }
    public Claims claims { get; set; }
    public Action()
    {
        odatatype = "microsoft.graph.tokenIssuanceStart.provideClaimsForToken";
        claims = new Claims();
    }
}

public class Claims{
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string CorrelationId { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string DateOfBirth { get; set; }
    public string ApiVersion { get; set; }
    public List<string> CustomRoles { get; set; }
    public Claims()
    {
        CustomRoles = new List<string>();
    }
}

Once you’ve got a Custom Claim Extension endpoint deployed, you’ll need to register the Custom Extension in the Azure Portal.

The final step, you should now assign the Custom Extension to one or more of your Enterprise Applications in the Entra ID Portal, from the Sign-On tab and grant Admin Consent, and that’s it.

At this point, it’s likely that all decisions will either be Allow or Deny, based on the Default Decision you’ve selected for the Entra ID Integration - if that’s the case, you’re ready to start assigning policies.

Assigning Policies

  1. Once the Entra ID integration is created, you can start assigning versions of Policies - to get started, select Policies from the tabs in your newly created integration

    SGNL - Policies

  2. Select ‘Assign Policies’

  3. Select:

    • The Policies you want to apply to the integration with the check box
    • The version of the Policy you want applied

    SGNL - Select Policies

  4. Click Next once you have the Policies and Versions configured as is appropriate

  5. Select the Enforcement mode for the Policies you chose in the previous step

    • Simulated: Policy Versions that are being simulated will only log their access decision in the SGNL logs and will not impact the access decision that SGNL hands back to an integration. Simulated policies are useful for performing what-if analysis of new policy versions as well as debugging policy changes.

      Note: It’s considered best practice to start with policies in Simulated mode, to verify that policies have been created an applied as expected

    • Enforced: Policy Versions that are being enforced will impact the access decisions that SGNL hands back to an integration. Enforced Policies will determine access for an integration

    SGNL - Set Enforcement

  6. Select your desired Enforcement mode and select Assign

  7. Versions of Policies will now be Assigned to your integration

    SGNL - Policy Assignments

    Configuring Actions

As part of the SGNL CAEP Hub, Entra ID can be configured to receive actionsfrom SGNL. Follow the steps below to configure Okta to enable this integration.

Configuring Entra ID

To configure the Entra ID Application:

  1. Login to the Azure Portal and browse to Entra ID
  2. From the Entra ID Nav, select ‘App Registrations’ and create a `New Registration’
  3. Give the integration a name, specify the scope of usage (recommended: select Accounts in this organizational directory only)
  4. For Redirect URI, select ‘Web’ and then use your SGNL OIDC client’s callback as the Redirect URI, e.g. https://console.sgnl.cloud/{{myClientName}}/auth/oidcCallback, then select Register
  5. Once Registered, from the ‘Overview’ page, copy the ‘Application (client) ID’ and the ‘Directory (tenant) ID’
  6. Select Manage from the Nav and then ‘Certificates & secrets’, create a ‘New client secret’ and copy the ‘Value’ (Note: Be careful to copy the value and not the ID of the secret)
  7. The last thing you’ll need to do in the Azure Portal is configure permissions for this Application, select API permissions from within the Manage nav item
  8. Depending on what you’re looking to do with CAEP Hub Actions, permissions can vary pretty widely, select ‘Add a permission’ to get started
  9. Select ‘Microsoft Graph’ and ‘Application Permissions’, common permissions you’ll need for SGNL Actions:
  • Group.ReadWrite.All For Adding and Removing Users from Groups
  • User.ReadWrite.All for Enabling and Disabling Users
  • User.RevokeSessions.All for Revoking Sign-In Sessions
  • note Azure provides a wealth of different permissions, and with SGNL’s generic Webhook – you can enact almost any change in Azure, but you’ll need to read Azure’s documentation to determine the right set of permissions for that action, or contact SGNL Support
  1. Add the Permissions when complete, and choose to ‘Grant Admin Consent’ for your Directory
  2. To simplify SGNL configuration, you can directly select the appropriate endpoints for Entra ID from the ‘Overview’ page and then ‘Endpoints’ at the top of the page – you’ll need the ‘OAuth 2.0 authorization endpoint (v2)’ (if using Authorization Code Flow) and ‘OAuth 2.0 token endpoint (v2)’

Configuring SGNL

  1. From your Entra ID Protected System (with a type of at least AzureAD-1.0.0), select Actions
  2. Specify the Address for the Action, for now this can just be the Microsoft Graph URL, i.e. https://graph.microsoft.com/v1.0
  3. Specify your authentication method, if you’ve chosen ‘Application Permissions’, Client Credentials will be your preferred Authentication method, you can enter the Client Id and Client Secret you copied above
  4. For your Scope, you can explicitly request scopes for this integration, or use https://graph.microsoft.com/.default – not that if you are using Delegated Permissions (i.e. on Behalf of a User) and thus using the ‘Authorization Code Flow’ you’ll also need the offline_access scope (in addition to the default Graph API scope https://graph.microsoft.com/.default) added to enable SGNL to automatically refresh tokens
  5. Finally, specify your Token URL

Creating your first action

From within SGNL you can configure any change in the graph to trigger actions that are directed to downstream systems. In an example scenario, you may want to trigger an Add User to Group action in Entra ID when a User goes on-call in Pagerduty:

  1. Select CAEP Hub from the SGNL nav
  2. Select Triggers
  3. Create a new Trigger:
  • Give the Trigger a Name and optionally a Description
  • Select Pager Duty and Entra ID Users as the Nodes, as well as PagerDuty Oncall Entity
  • Select the SameAs relationship between the Entra ID User and the Pager Duty User, and select the On Call relationship between the Pager Duty User and Oncall Object
  • Save the Trigger
  1. Create a new Rule
  • Give the Rule a Name and optionally a Description
  • Select the Trigger you just created
  • Select the option for when the ‘Matches Trigger Scope’
  • Click the button to ‘Select an Action’
  • Choose ‘Entra ID’ and the ‘Add to Group’ Action
  1. Configure the required fields
  • The userPrincipalName should be the UPN of the Entra ID User: e.g. {$.EntraIDUser.userPrincipalName}
  • The groupId should be the Id of the Group you want the user added to, this can be statically assigned (e.g. 05f35c53-8393-489d-ac18-b57d4ab7a97e) or retrieved from an object in the graph, e.g. {$.PagerDutyOnCall.escalation_policy_summary}
  1. Save the Rule

That’s it. Once you’ve finished configuring your rule, the next time a User goes oncall within PagerDuty, they will be added to the group specified in the Rule. Now is the time you might want to think about creating a Rule that uses the No Longer Matches Trigger Scope type of Graph Change to remove the user from the Group (and possibly send a Slack notification) when they are no longer oncall.