Integrating your Identity Provider
Supports Auth0, Microsoft Azure, AWS Cognito, and AWS IAM Identity Providers
Identity Provider Integration
Next Level3 supports integrations to a variety of best in class identity providers. The video below provides an example of how identity provider integrations work.
How Identity Providers are Integrated:
The instructions below provide the steps required to setup and configure the integration for each of these providers. To get started first choose the identity provider your application uses and then follow the instructions to enable Next Level3 Cloud Identity JIT Access for that provider.
Identity Provider Integration Instructions
Auth0 Integration
The Next Level3 Auth0 integration is designed to be used for any of your existing applications or sites which are using Auth0 for authentication. This integration will allow you to easily add Account Protection to any application that leverages Auth0 for authentication.
Pre-requisites
- Auth0 Application
- Next Level3 Company Account
- Signing Key created for an application in the Next Level3 Company Portal
NL3 Auth0 Action for Performing Account Protection Check
This integration allows Auth0 customers to integrate a post login action to check the lock status for their users’ accounts and block access if locked. This integration requires an active license with Next Level3. Please visit https://www.nextlevel3.com for more details on how to sign up for NL3 Account Protection.
Steps for configuring as a custom action
- Log into manage.auth0.com as a user with permissions to create and modify custom actions
- In the left-side menu select Actions > Library
- Select the “Build Custom” button in the upper right-hand corner
- Give the action a descriptive name in the “Name” field (e.g. NL3 Account Protection Check for MyApp)
- Select “Login / Post Login” for “Trigger”
- Select “Node 16 (Recommended)” for “Runtime”
- Click “Create”
- Copy and Paste the code from below into the action (replace any current, default code completely)
const https = require('https');
const nJwt = require('njwt');
function getLockStatus (jwt, api_host, api_path, event) {
return new Promise((resolve, reject) => {
var post_data = JSON.stringify({ "userIP": event.request.ip,
"userDevice": event.request.user_agent,
"userLocation": event.request.geoip.cityName,
"integrationType": "auth0",
"integrationData": event.request
})
var options = {
host: api_host,
port: "443",
path: api_path,
method: "POST",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'x-nl3-authorization-token': jwt,
'x-nl3-device-location': event.request.geoip.latitude + ',' + event.request.geoip.longitude,
'x-forwarded-for': event.request.ip,
'User-Agent': event.request.user_agent,
'Content-Length': Buffer.byteLength(post_data)
}
}
const req = https.request(options, (response) => {
let chunks_of_data = [];
response.on('data', (fragments) => {
chunks_of_data.push(fragments);
});
response.on('end', () => {
let response_body = Buffer.concat(chunks_of_data);
if (response.statusCode == 200) {
resolve(response_body.toString());
}
else {
resolve('{ "statusCode": ' + response.statusCode + '}');
}
});
response.on('error', (error) => {
console.log("Error = " + error.message);
reject(error);
});
});
req.write(post_data);
req.end();
});
}
exports.onExecutePostLogin = async (event, api) => {
var failOpen = event.secrets.FAIL_OPEN == 'true'
try {
if (event.client.client_id==event.secrets.CLIENT_ID) {
var claims = {
iss: event.secrets.APP_URI,
aud: event.secrets.API_HOST,
sub: event.user.name
}
let decodedDomainToken = Buffer.from(event.secrets.SIGNING_KEY, 'base64');
var jwt = nJwt.create(claims, decodedDomainToken);
jwt.setExpiration(new Date().getTime() + (60*5*1000)); //5 minute expiration to allow for SignUp
jwt.setNotBefore(new Date().getTime() - (60*1*1000)); //Valid from 1 minute ago to account for minor time diffs
var authToken = jwt.compact();
if (authToken.length > 0) {
console.log(event.secrets.API_HOST + event.secrets.API_PATH)
const res = await getLockStatus (authToken, event.secrets.API_HOST, event.secrets.API_PATH, event);
if(res.indexOf('404') == -1)
var result = JSON.parse(res);
else
console.log(res.toString());
if(result) {
if(!result.statusCode) {
console.log(JSON.stringify(result));
if(result.locked) {
api.access.deny(event.secrets.LOCKED_MESSAGE);
}
} else {
if (!failOpen) {
api.access.deny(event.secrets.LOCKED_MESSAGE);
}
}
} else {
if (!failOpen) {
api.access.deny(event.secrets.LOCKED_MESSAGE);
}
}
}
}
} catch (err) {
if (!failOpen) {
api.access.deny(event.secrets.LOCKED_MESSAGE);
}
}
};
- Select the icon that looks like a package called “Modules”
- Select “Add Module”
- In the “Name” textbox type njwt
- Click “Create”
- Select the icon that looks like a skeleton key called “Secrets”
- Add the following secrets:
- SIGNING_KEY - The base64 encoded signing key associated with the application you are integrating with from the NL3 company portal (company.nextlevel3.com)
- APP_URI - The fully-qualified domain name associated with your application and SIGNING_KEY
- CLIENT_ID - The Auth0 Client ID associated with the application you wish to add an NL3 Account Protection Check (This value can be found at manage.auth0.com by selecting Applications > Applications on the left-side menu. It will be in the right-hand column of the list of applications.
- API_HOST - The domain name for the NL3 external API (e.g., api.nextlevel3.com - see NL3 product documentation or contact your account representative)
- API_PATH - The path to the account protection check API method (e.g., /nl3/api/v1/accountProtectionCheck)
- LOCKED_MESSAGE - The message to display to the end user if the account is locked (e.g. “Either the username and/or password are incorrect or the user account is locked”)
- FAIL_OPEN - Set to ‘true’ without quotes if you want the lock check to fail open, otherwise set it to ‘false’ without quotes.
Fill in POST data (post_data) with appropriate Auth0 fields in promiseEU function
Different customers store user data in Auth0 in different fields. You will most likely need to update the event.user… properties referenced to the appropriate event.user fields for your configuration (contact support for guidance).
Integrate into Actions “Login” Flow
- Log into manage.auth0.com as a user with permissions to create and modify action flows
- In the left-side menu select “Actions > Flows”
- Select “Login” from the tiles under the “Flows” header
- In the side panel with the “Add Action” header, select the “Custom” tab
- Find the Custom Action you created above and drag it into one of the “Drop Here” boxes that display below the “Start > User Logged In” and “Complete - Token Issued” flow designators when you start to drag over the custom action and make sure when you release it the action remains in the flow
- Select “Apply” in the top right to add the action to the flow
Test Integration
First, enable a user account for this application. Then, attempt to authenticate with the user account locked and then again with the user account unlocked.
Microsoft Azure AD B2C Integration
The Next Level3 Azure AD B2C integration is designed to be used for any of your existing applications or sites which are using Azure AD for authentication. This integration will allow you to add Account Protection for any application which leverages Azure AD B2C for authentication.
Pre-requisites
- Application leveraging Azure AD B2C for authentication
- Next Level3 Company Account
- Signing Key created for an application in the Next Level3 Company Portal
Enabling Next Level3 Azure AD B2C Custom Policy to Sign In
The first step to add an NL3 Account Protection Check to an existing application that is using Azure AD B2C for authentication is to create a custom policy to add the check to your existing login flow. Unless you are very familiar with Azure AD B2C policies and user journeys, we recommend downloading the starter packs from here: GitHub project.
For most of you, assuming you have already implemented Azure AD B2C, you may already be familiar with the policies you are using and you can start with those policies.
For this integration, we used the standard ‘Local Accounts’ starter pack downloaded from the GitHub repository referenced above. The only change we made to the policies listed in the ‘Local Accounts’ folder of that repo was in the TrustFrameworkBase.xml policy. We added the following ‘ClaimsProvider’ to line 453 underneath the existing ‘Local Account SignIn’ ‘ClaimsProvider’:
<ClaimsProvider>
<DisplayName>Local Account NL3 Protection Check</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="REST-NL3AccountProtectionCheck">
<DisplayName>Perform NL3 Account Protection Check</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">https://[your-service-endpoint].azurewebsites.net/api/AccountProtectionCheck</Item>
<Item Key="AuthenticationType">ApiKeyHeader</Item>
<Item Key="SendClaimsIn">Body</Item>
</Metadata>
<CryptographicKeys>
<Key Id="x-functions-key" StorageReferenceId="B2C_1A_RestApiKey" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
The ‘ServiceUrl’ points to an API endpoint that runs the code for performing the NL3 Account Protection Check. We have deployed this as an Azure Function, but as long as it is a RESTful API endpoint that validates the API key in the headers, performs the Protection Check, and returns the proper status codes and messages, the technology used is not important. We will use an Azure Function for this example. If you are not familiar with Azure Functions, the following guides can be helpful: https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-function-app-portal
https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-node
The function in this example uses the Node.js 16 runtime and this is the code you can use within your function to perform the protection check:
The next step is to setup the Amazon EventBridge event rule that triggers the Lambda function for specific events. Here is a sample rule:
References:
https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-get-started.html
Microsoft Azure AD and O365 Integration
This section will walk you through the required setup and configuration steps needed to enable the Next Level3 Cloud Identity solution in a Microsoft online environment. These steps include setting up and configuring a Microsoft AD FS Farm (if one does not already exist), setting up and configuring the Next Level3 Cloud Identity Account Protection Check by using a Microsoft AD FS Risk Assessment Model Plugin. If you have already federated your Microsoft Online services with AD FS, skip the pre-requisites and go directly to the plugin installation and setup.
Pre-requisites
- Install and Configure an AD FS Server Farm
- Choose Option A or B
- Option A – use Option A if you want to install and configure AD FS yourself
- Option B if you want to use an ARM template to set up an AD FS farm in Azure, and skip this step altogether if you already have an AD FS Server Farm 2016 or later.
Option A - Install and Configure AD FS on Windows Server 2022 or 2019 on-premises
Installation and configuration of AD FS on Windows Service 2022 or 2019 is the first step. This can be done in an on premise or cloud hosted environment depending on your specific needs. To get started with this process follow the documentation found here: GUIDE FOR INSTALLATION, CONFIGURATION, AND DEPLOYMENT
- https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/deployment/windows-server-2012-r2-ad-fs-deployment-guide
- https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/deployment/deploying-a-federation-server-farm
- https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/deployment/deploying-federation-server-proxies
Option B - Install and Configure AD FS in Azure
GUIDE FOR DEPLOYING IN AZURE INCLUDING ARM TEMPLATE skip to “Step-by-step Instructions for Using ARM Template to Deploy AD FS Farm” for automated setup instructions
Step-by-step Instructions for Using ARM Template to Deploy AD FS Farm
-
Click Deploy to Azure in README.md for the following repository (https://github.com/Next-Level3/adfs-6vms-regular-template-based-server-2022)
-
Log into Azure as an account with permissions to deploy Virtual Networks, Load Balancers, and Virtual Machines
-
Fill in template parameters as follows:
- Subscription – celect appropriate Subscription from drop-down.
- Resource Group – create a new Resource group and select from drop-down.
- Region – inherited from resource group (cannot edit)
- Location – enter region/location for resources (e.g., East US)
- Storage Account Type – choose appropriate option from drop-down.
- Virtual Network Usage – select “new” from drop-down.
- Virtual Network Name – enter name for new virtual network.
- Virtual Network Resource Group Name – n/a.
- Virtual Network Address Range – leave defaults unless changes are needed for your environment.
- Internal Subnet Name – leave defaults unless changes are needed for your environment.
- Internal Subnet Address Range – leave defaults unless changes are needed for your environment.
- Dmz Subnet Address Range – leave defaults unless changes are needed for your environment.
- Dmz Subnet Name – leave defaults unless changes are needed for your environment.
- Addc01Nic IP Address – leave defaults unless changes are needed for your environment.
- Addc02Nic IP Address – leave defaults unless changes are needed for your environment.
- Adfs01Nic IP Address – leave defaults unless changes are needed for your environment.
- Adfs02Nic IP Address – leave defaults unless changes are needed for your environment.
- Wap01Nic IP Address – leave defaults unless changes are needed for your environment.
- Wap02Nic IP Address – leave defaults unless changes are needed for your environment.
- Adfs Load Balancer Private Ip Address – leave defaults unless changes are needed for your environment.
- Add VM Name Prefix – leave defaults unless changes are needed for your environment.
- Adfs VM Name Prefix – leave defaults unless changes are needed for your environment.
- Wap VM Name Prefix – leave defaults unless changes are needed for your environment.
- Add VMs Size – leave defaults unless changes are needed for your environment (recommend Standard_B2s for pre-production environments to save money).
- Adfs VMs Size – leave defaults unless changes are needed for your environment (recommend Standard_B2s for pre-production environments to save money).
- Wap VMs Size – leave defaults unless changes are needed for your environment (recommend Standard_B2s for pre-production environments to save money).
- Admin Username – fill in with the desired username.
- Admin Password – fill in with the desired password.
- Click “Review + create”.
- Click “Create”.
-
After resources are created, connect to wap01 via RDP over port 5000 by getting Frontend IP from the wap-lb resource.
- Example Frontend IP Configuration.
- Example Remote Desktop (RDP) Connection configuration.
-
Connect using the admin username and password configured in the corresponding deployment parameters.
-
Connect to other resources to configure them by using the RDP client on wap01 to any of the other servers for configuration.
-
Set up the domain and join ADFS servers (if the existing domain is not available for federation).
- Connect to dc01 at 10.0.0.101 from wap01.
- Install and configure Active Directory Domain Services by following these instructions (https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/deploy/install-active-directory-domain-services–level-100-).
- Connect to dc02 at 10.0.0.102 and install and configure it as a secondary domain controller.
- Connect to adfs01 at 10.0.0.201 from wap01 and join it to the domain created in the previous steps (you may need to update the DNS servers in the network interface adapters to point to dc01 and dc02.
- Connect to adfs02 at 10.0.0.202 from wap01 and join it to the domain created in the previous steps. Pre-requisite 1 – Not required if O365 or other Microsoft Online services are already in use by your company.
-
Set up an Azure AD tenant and custom domain for your Active Directory domain that will be federated with Microsoft Online (O365, Azure AD, etc.). Pre-requisite 2 Azure AD Domain Setup
- Create corresponding domain in Azure AD.
- Connect and log in to https://portal.azure.com with admin credentials.
- Search for “Azure Active Directory” and choose that service from the results.
- Select “Manage Tenants” at the top of the “Overview” screen.
- Click “Create” on the “Manage Tenants” screen.
- Choose “Azure Active Directory” and click “Next: Configuration >”.
- Fill in the details for the Domain created in the last section or an existing domain.
- For “Initial domain name” use the domain prefix for your domain as you will be required to set up a “Custom domain” to get it to match the FQDN of your domain.
- After entering the configuration details, select “Next: Review + create >”, then “Create”, and finally submit any captcha to complete the process.
- After completion, click on the link to the new domain and sign in with the credentials used to create the domain.
- Select “Custom domain names” from the side menu, then “Add custom domain” at the top of the resulting configuration screen.
- Enter the FQDN of the existing domain you would like to federate through AD FS and click “Add custom domain”.
- Create TXT or MX records in DNS for the domain to verify ownership.
- After creating the records and confirming they are resolving, click “Verify”.
- Do NOT click “Make Primary”
- Create a user account in the new Azure AD Domain with the <domain>.onmicrosoft.com prefix and add the “Hybrid Identity Administrator” role to that user.
-
Search for “Azure Active Directory” and select that service.
-
In the top menu, select “New user”, “Create new user”.
-
Enter the user’s details on the configuration screens until reaching “Assignments”.
-
On the “Assignments” screen, select “+ Add role”.
-
Search for “Hybrid Identity Administrator” and check the box next to it before clicking “Select”.
-
Search for “Global Administrator” and check the box next to it before clicking “Select”.
-
Select “Next: Review + create >”, then “Create”.
-
Confirm the roles have been assigned under “Assigned roles”.
-
If not, click “Add assignments” and search for “Hybrid Identity Administrator” and “Global Administrator” again and click “Add”.
-
Click “Refresh” to confirm the role has been added.
After the AD FS Farm servers are available and the Azure AD tenant and domain are set up, the next step is to configure AD FS on the server infrastructure and federate the Microsoft Online domain with the AD FS Farm, if these steps have not already been completed. If you have already set up “Azure AD Connect” to synchronize your on-premises domain with Azure AD but have not yet set up an AD FS server farm or federation, use Option B.
-
- Create corresponding domain in Azure AD.
Pre-requisite 3. Configure AD FS for Single Sign-On
Option A - Assisted setup with new Azure AD Connect installation and configuration
- Install and run “Azure AD Connect” on a domain joined server.
- Turn off Internet Explorer’s (IE) Enhanced Security Configuration under “Server Manager” > “Local Server”.
- Add https://secure.aadcdn.microsoftonline-p.com and https://login.microsoft.com to the “Trusted Sites” zone in IE.
- Download “Azure AD Connect” from here or from the latest link provided after verifying the domain (https://www.microsoft.com/en-us/download/details.aspx?id=47594).
- Install “Azure AD Connect” using the downloaded MSI.
- Check “I agree to the license terms and privacy notice.” and then click “Continue.”
- Select “Customize”.
- Leave everything unchecked under “Install required components”, unless you desire additional customization, and then click “Install”.
- On the “User sign-in” screen, select “Federation with AD FS”, then select “Next”.
- On the “Connect to Azure AD” screen, enter the credentials for the “Hybrid Identity/Global Administrator” account created when completing the “Azure AD Domain Setup” steps (be sure to use the <domain>.onmicorosft.com username suffix) and click “Next”.
- Re-enter the “Hybrid Identity/Global Administrator” account username with the <domain>.onmicrosoft.com suffix on the “Sign In” screen that pops up.
- If required to update the password on the first login, do so.
- On the “Connect your directories” screen, select the domain you wish to federate and click “Add Directory”.
- On the “AD Forest Account” select “Create new AD account” and enter an “Enterprise Admin” username and password for the on-premises domain and click “OK”.
- Select “Next” and under “Azure AD sign-in configuration” confirm the Active Directory UPN Suffix is displayed and that it shows “Verified” under “Azure AD Domain”.
- Under “Select the on-premises attribute to use as the Azure AD username” select “userPrincipalName” which should be the default and select “Next”.
- If necessary, filter the domains or OUs you wish to synchronize. If not, leave it as “Sync all domains and OUs”.
- Under “Identifying Users”, leave the defaults unless you have users who exist in multiple directories (if this is the case, consult the Azure AD Connect installation instructions here https://learn.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-install-custom#select-how-users-should-be-identified-in-your-on-premises-directories), click “Next”.
- If desired, filter synchronization for specific users or groups. If not leave the defaults and click “Next”.
- Configure “Optional features” as needed or leave defaults and select “Next”.
- Enter a “Domain Administrator” account for the domain in which AD FS will be deployed (the domain you are federating) and click “Next”.
- Continue creating a new AD FS Farm
- Select “Configure a new AD FS farm” and browse to a certificate file for the domain you will be using for your AD FS farm FQDN and enter the password for the PFX file you select (see certificate requirements here learn.microsoft.com/en-us/windows-server/identity/ad-fs/design/certificate-requirements-for-federation-servers).
- Select the appropriate “SUBJECT NAME” from the certificate for your AD FS form and then enter the prefix you want to assign to your farm to ensure it matches the “SUBJECT NAME” defined in the certificate (unless using a wildcard certificate in which case the prefix you wish to use for the farm; e.g., adfs, sso, sts) and click “Next”.
- Enter the server name or IP address under SERVER for your primary ADFS server, click “Add”, and after validation click “Next”.
- Make sure the “Web Application Proxy” server resolves the AD FS farm FQDN to the internal IP address of the AD FS farm server by using internal DNS or the hosts file (typically found at C:\Windows\System32\drivers\etc\hosts). Also, include an entry for the FQDN of the host as produced by Active Directory (e.g., <hostname>.<subdomain>.<domain>.com)
- Make sure the Azure AD Connect server, resolves a Web Application Proxy Hostname to the IP address of the Web Application Proxy in the DMZ using internal DNS or the hosts file (typically found at C:\Windows\System32\drivers\etc\hosts) and that WinRM is allowed through firewalls.
- Enable PS-Remoting on the Azure AD Connect Server and the Proxy by running the following commands from an elevated PowerShell session.
- On the proxy, run “Enable-PSRemoting -force” then “Set-Item WSMan:\localhost\Client\TrustedHosts -Value <AADConnectServerFQDN>” replacing <AADConnectServerFQDN> with the FQDN for the Azure AD Connect server and confirming with “Y” when prompted, then, “Restart-service -name winrm”.
- On the AD connect server, run “Set-Item WSMan:\localhost\Client\TrustedHosts -Value <DMZServerHostname> -Force -Concatenate”, run “Set-Item WSMan:\localhost\Client\TrustedHosts -Value <DMZServerIPAddress> -Force -Concatenate”, then “Restart-service -name winrm”.
- Either change the internal facing network adapter for the proxy to “Private” or add firewall rules to allow WinRM from the Azure AD Connect server.
- Enter the Hostname or IP address (if the Proxy server is not joined to a domain with a proper trust relationship, use the FQDN setup above and provide credentials for the proxy; also, if an error is received about not being able to connect, open PowerShell on the proxy and run Enable-PSRemoting) for the proxy server and click “Add”, then click “Next”.
- Select “Create a group Managed Service Account” and fill in the “Enterprise Admin” username and password for the domain again and click “Next”.
- Select the Azure AD domain with which you want to federate from the “DOMAIN” drop-down and select “Next”.
- Under “Ready to configure”, leave defaults and select “Install” unless this is a production environment, and then consider selecting “Enable staging mode”.
- On the “Configuration complete” screen if no errors, click “Next”.
- Create an internal DNS record that points your AD FS farm FQDN to the internal IP address of your AD FS farm for internal users and also an external DNS record that points to the external IP address for your “Web Application Proxy” then select both checkboxes and click “Verify”.
- If there are no errors, click “Exit”.
- Manually validate by browsing to https://<ADFSFQDN>/adfs/fs/federationserverservice.asmx from inside the network and from the proxy which should display an XML document.
- Publish the proxy pass-through application by opening “Remote Access Management” from the start menu and selecting the proxy name and then “Publish” in the right-hand menu.
- Select “Next”.
- Select “Pass-through” and “Next”
- Enter a “Name:” (e.g., ADFS), “External URL:” (FQDN of AD FS farm; e.g., https://adfs.domain.com), select the appropriate certificate under “External certificate:” drop-down, and click “Next”.
- Click “Publish”.
- Then, validate outside the network by browsing to https://<ADFSFQDN>/adfs/ls where you should get a page with an error message.
- If the request times out, ensure the Windows Firewall allows HTTP & HTTPS or ports 80 & 443 Inbound.
- Finally, test the federation by going to https://portal.azure.com and logging in with one of the accounts for the federated domain. If you do not get redirected to your AD FS FQDN for login after entering the username, there may have been an error during the automated setup. If this is the case (DO NOT PERFORM THE FOLLOWING STEPS IF YOU ARE REDIRECTED), try the following:
- Re-open Azure AD Connect, select “Configure”, then “Manage federation”, then “Next”.
- On the “Manage federation” screen, select “Federate Azure AD domain”, then “Next”.
- Enter the password for your Azure AD “Hybrid Identity/Global Administrator” and then click “Next”. You may have to re-enter the credentials in a pop-up window after clicking “Next”.
- On the “Connect to AD FS” screen enter administrator credentials for your AD FS farm.
- On the “Azure AD domain” screen, select the domain you wish to federate, validate the information, and click “Next”.
- On the “Azure AD trust” screen, take note of changes it will make on your behalf and click “Next”.
- Finally, click “Configure”.
- Retry connecting to https://portal.azure.com to ensure it redirects to a federated user account.
- If you used the Azure Deployment above, change the “Web Application Proxy” servers to point to the load balancer IP address for the AD FS farm (e.g., 10.0.0.200).
- Configure Secondary AD FS Server and Web Application Proxy servers Option B – Assisted setup with existing Azure AD Connect installation
- Open “Azure AD Connect” on the synchronization server and click “Configure”.
- Click “Manage federation”, then “Next”.
- Click “Managed servers”, then “Next”.
- Select the appropriate option for deploying either a server, a proxy, or connecting to a current AD FS farm and click “Next”
- For an AD FS server
- On the “Connect to AD FS” screen, enter administrator credentials for your AD FS farm.
- On the “Specify SSL certificate” screen, browse to a certificate file for the domain you will be using for your AD FS farm FQDN and enter the password for the PFX file you select (see certificate requirements here learn.microsoft.com/en-us/windows-server/identity/ad-fs/design/certificate-requirements-for-federation-servers). If you are adding a secondary server, just enter the password for you existing certificate.
- Select the appropriate “SUBJECT NAME” from the certificate for your AD FS form and then enter the prefix you want to assign to your farm to ensure it matches the “SUBJECT NAME” defined in the certificate (unless using a wildcard certificate in which case the prefix you wish to use for the farm; e.g., adfs, sso, sts) and click “Next” (these may already be selected for you if this is a secondary server).
- Enter the fully-qualified server name for the AD FS server and click “Add”, then click “Next”.
- Click “Configure”.
- For a Web Application Proxy
- Enable PS-Remoting on the Azure AD Connect Server and the Proxy by running the following commands from an elevated PowerShell session.
- On the proxy, run “Enable-PSRemoting -force” then “Set-Item WSMan:\localhost\Client\TrustedHosts -Value <AADConnectServerFQDN>” replacing <AADConnectServerFQDN> with the FQDN for the Azure AD Connect server and confirming with “Y” when prompted, then, “Restart-service -name winrm”.
- On the AD connect server, run “Set-Item WSMan:\localhost\Client\TrustedHosts -Value <DMZServerHostname> -Force -Concatenate”, run “Set-Item WSMan:\localhost\Client\TrustedHosts -Value <DMZServerIPAddress> -Force -Concatenate”, then “Restart-service -name winrm”.
- Either change the internal facing network adapter for the proxy to “Private” or add firewall rules to allow WinRM from the Azure AD Connect server.
- On the “Connect to AD FS” screen, enter administrator credentials for your AD FS farm.
- On the “Specify SSL certificate” screen, browse to a certificate file for the domain you will be using for your AD FS farm FQDN and enter the password for the PFX file you select (see certificate requirements here learn.microsoft.com/en-us/windows-server/identity/ad-fs/design/certificate-requirements-for-federation-servers). If you are adding a secondary server, just enter the password for you existing certificate.
- Select the appropriate “SUBJECT NAME” from the certificate for your AD FS form and then enter the prefix you want to assign to your farm to ensure it matches the “SUBJECT NAME” defined in the certificate (unless using a wildcard certificate in which case the prefix you wish to use for the farm; e.g., adfs, sso, sts) and click “Next” (these may already be selected for you if this is a secondary server).
- Enter the Hostname or IP address (if the Proxy server is not joined to a domain with a proper trust relationship, use the FQDN setup above and provide credentials for the proxy; also if an error is received about not being able to connect open PowerShell on the proxy and run Enable-PSRemoting) for the proxy server and click “Add”, then click “Next”.
- After validation, click “Configure”.
- Enable PS-Remoting on the Azure AD Connect Server and the Proxy by running the following commands from an elevated PowerShell session.
- Once all servers are set up and configured, open “Azure AD Connect” again and click “Configure”.
- Click “Manage federation”.
- Click “Federate Azure AD domain”.
- Enter the username and password for an Azure AD user with both the “Hybrid Identity & Global Administrator” roles and then click “Next”. You may have to re-enter the credentials in a pop-up window after clicking “Next”.
- On the “Connect to AD FS” screen enter administrator credentials for your AD FS farm.
- On the “Azure AD domain” screen, select the domain you wish to federate, validate the information, and click “Next”.
- On the “Azure AD trust” screen, take note of changes it will make on your behalf and click “Next”.
- Finally, click “Configure”. After the domain is federated with AD FS, enable NL3 Account Protection using the Microsoft AD FS Risk Assessment Model Plugin and sample code provided by NL3.
Install Microsoft AD FS Risk Assessment Model Plugin for the Account Protection Check.
- Follow the instructions in the following GitHub repository in the README.md file:
- https://github.com/Next-Level3/nl3-adfs-plugin
- When filling out the appConfig.csv file, use “urn:federation:Microsoft” without quotes for the LookupKey associated with the AppName and SigningKey you create in the Next Level3 Company Portal for Microsoft Online services and applications.
- For steps on setting up your application and generating and validating your signing key for Microsoft Online services, please see <placeholder>.
- Enable users for account protection in the Next Level3 Company Portal. Make sure to use the fully qualified user account (e.g., <username>@<subdomain>.<domain>.com). It may be possible to use other SAML Identity Providers if the SAMLp protocol is supported.
Instructions for Third-party SAMLp providers
While the following instructions favor AD FS, they should work with any identity provider that supports SAMLp which has differences from the regular SAML protocol. Compatible identity providers will support “SAML 2.0 compliant SP-Lite profile-based Identity Provider” standards.
https://learn.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-fed-saml-idp
const https = require("https");
const nJwt = require("njwt");
function getLockStatus(jwt, apiHost, apiPath, requestHeaders) {
return new Promise((resolve, reject) => {
const postData = JSON.stringify({
userIP: requestHeaders["x-forwarded-for"],
userDevice: requestHeaders["user-agent"],
userLocation: "",
integrationType: "aadb2c",
integrationData: {},
});
const options = {
host: apiHost,
port: "443",
path: apiPath,
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"x-nl3-authorization-token": jwt,
"Content-Length": Buffer.byteLength(postData),
},
};
const req = https.request(options, (response) => {
const chunksOfData = [];
response.on("data", (fragments) => {
chunksOfData.push(fragments);
});
response.on("end", () => {
const responseBody = Buffer.concat(chunksOfData);
resolve(responseBody.toString());
});
response.on("error", (error) => {
console.log(`Error = ${error.message}`);
reject(error);
});
});
req.write(postData);
req.end();
});
}
module.exports = async function (context, req) {
let responseMessage = "";
let responseStatus = 200;
const claims = {
iss: process.env.APP_URI,
aud: process.env.API_HOST,
sub: req.body.signInName,
};
/* Ideally this Signing Key would be stored and retrieved from a secrets manager
and not an environmental variable */
const decodedDomainToken = Buffer.from(process.env.SIGNING_KEY, "base64");
const jwt = nJwt.create(claims, decodedDomainToken);
jwt.setExpiration(new Date().getTime() + 60 * 5 * 1000); // 5 minute expiration
jwt.setNotBefore(new Date().getTime() - 60 * 1 * 1000); // 1 minute leeway
const authToken = jwt.compact();
const res = await getLockStatus(
authToken,
process.env.API_HOST,
process.env.API_PATH,
req.headers
);
const result = JSON.parse(res);
let failed = false;
if (result) {
context.log(JSON.stringify(result));
if (Object.prototype.hasOwnProperty.call(result, "locked")) {
if (result.locked) {
responseStatus = 409;
responseMessage = process.env.LOCKED_MESSAGE;
}
} else {
failed = true;
}
} else {
failed = true;
}
if (failed) {
if (process.env.FAIL_CLOSED == "true") {
responseStatus = 409;
responseMessage = "NL3 Account Protection Check failed and configuration is set to fail closed!";
}
}
context.res = {
status: responseStatus,
body: responseMessage
};
};
The environmental variables for API_HOST, API_PATH, APP_URI, FAIL_CLOSED, LOCKED_MESSAGE, and SIGNING_KEY can be set by opening your Function App in the Azure Portal, then clicking on ‘Configuration’ and adding each as a ‘New application setting’ under ‘Application settings’. We have added all values as environmental variables for simplicity, but it is recommended that the ‘SIGNING_KEY’ be stored in a secrets manager like Azure Key Vault instead of being stored as an environmental variable when possible which will require some minor updates to the code (please contact support for guidance). The URL to use for your ‘ServiceUrl’ in custom policy can be found in your ‘Function App’ under ‘Functions’, then click on your function’s name, then click on the ‘Get Function Url’ selection at the top (you can remove the ?code= … parameter at the end since we will be providing that code in the headers).
Once your function is created and you have updated the ‘ServiceUrl’ in your custom policy, you will need to deploy the custom policy in Azure. If you have already set up a custom policy to support your application previously, you will only need to upload the modified ‘TrustFrameworkBase.xml’ policy. However, if you have not previously leveraged custom policy, guidance can be found here on how to set up the pre-requisites and upload a policy:
Then, depending on the type of application you are integrating, you will need to update the corresponding settings to point to the custom policy. Examples are provided for a variety of application types listed under ‘Next Steps’ in the above-referenced tutorial.
The next step is to setup the Amazon EventBridge event rule that triggers the Lambda function for specific events. Here is a sample rule:
References:
https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-get-started.html
AWS Cognito Integration
The Next Level3 AWS Cognito integration is designed to be used for your existing applications or sites that are using AWS Cognito for authentication. This integration will allow you to easily add Account Protection to any application that leverages AWS Cognito for authentication.
Pre-requisites
- Application Authenticated via Amazon Cognito User Pools
- Next Level3 Company Account
- Signing Key created for an application in the Next Level3 Company Portal
Account Protection
The first step to add an NL3 Account Protection Check to an existing application that uses Amazon Cognito User Pools for authentication is to create a Lambda function that performs the lock check. Here is some sample Python code:
import json
import os
import requests
import base64
import logging
from datetime import datetime
import jwt
def getLockStatus(token, api_uri, api_path, validationData):
responseDict = {}
try:
headers_dict = {"x-nl3-authorization-token": token, "Content-Type": "application/json"}
data_dict = {
"userIP": validationData["ip"],
"userDevice": validationData["device"],
"userLocation": validationData["location"],
"integrationType": "cognito",
"integrationData": json.loads(validationData["additionalData"])
}
response = requests.post("".join([api_uri,api_path]), headers=headers_dict, json=data_dict)
responseDict = response.json()
except Exception as e:
responseDict = { "message": str(e) }
return responseDict
def lambda_handler(event, context):
if event["callerContext"]["clientId"] == os.environ["CLIENT_ID"]:
username = event["userName"]
claims = {
"iss": os.environ["APP_URI"],
"iat": (datetime.utcnow().timestamp() + (-1 * 60)),
"exp": (datetime.utcnow().timestamp() + (5 * 60)),
"aud": os.environ["API_URI"],
"sub": username
}
### Ildeally the Signing Key would be stored and retrieved from a secrets manager
### and not an environmental variable
decodedDomainToken = base64.b64decode(os.environ["SIGNING_KEY"])
token = jwt.encode(
payload=claims,
key=decodedDomainToken
)
response = getLockStatus(token, os.environ["API_URI"], os.environ["API_PATH"], event["request"]["validationData"])
if response.get("locked", False):
raise Exception(os.environ["LOCKED_MESSAGE"])
# Return to Amazon Cognito
return event
The next step is to configure the Amazon Cognito User Pool to call this Lambda function as a “Pre authentication” trigger by clicking on the User Pool and then selecting “Triggers” under “General Settings” in the side menu. Then, you will select the function you created in the drop-down box under “Pre authenticaiton” as follows:
*If you use IAM Roles and not IAM Users, please contact support to discuss the options for implementation and specific design considerations that may be required to implement this correctly using roles.
The next step is to setup the Amazon EventBridge event rule that triggers the Lambda function for specific events. Here is a sample rule:
References:
https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-get-started.html
AWS IAM Integration
The Next Level3 AWS IAM integration is designed to be used for your existing applications or sites that are using AWS IAM for authentication. This integration will allow you to easily add Account Protection to any application that leverages AWS IAM for authentication.
Pre-requisites
- AWS Account Leveraging IAM Users or Roles*
- Next Level3 Company Account
- Signing Key created for an application in the Next Level3 Company Portal
Account Protection
The AWS IAM integration is slightly different from other integrations. AWS does not provide access to the login flow for IAM users or roles so there is no way to directly implement the lock check. However, via Amazon EventBridge, we can trigger a lock check on login and, if the account is locked, apply a “Deny All” managed policy, a “Revoke Sessions” policy, and even disable any active access keys. If the account is unlocked, the event would ensure that the policies are removed and the access keys that were most recently used are re-activated. Please be aware that there is a small delay between the login event and any policy being applied or removed. Also, if the Amazon EventBridge event is not triggered correctly, the lock check will not be performed and no policy will be applied.
The first step for setting up this integration is to create a Lambda function that can perform the lock check when a login event occurs and apply or remove the policies. Here is some sample code for a regular IAM user written in Python:
import json
import os
import requests
import base64
import logging
from datetime import datetime
import time
import jwt
import boto3
from botocore.exceptions import ClientError
iamClient = boto3.client('iam',region_name="us-east-1")
logger = logging.getLogger()
logger.setLevel(logging.INFO)
policyDocument = "{ \"Version\": \"2012-10-17\", \"Statement\": [ { \"Action\": [ \"*\" ], \"Effect\": \"Deny\", \"Resource\": \"*\" } ] }"
awsRevokeOlderSessionsPolicyDocument = "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [{\n \"Effect\": \"Deny\",\n \"Action\": [ \"*\" ],\n \"Resource\": [ \"*\" ],\n \"Condition\": {\n \"DateLessThan\": {\n\"aws:TokenIssueTime\": \"" + datetime.now().strftime("%Y-%m-%dT%H:%M:%S.000Z") + "\"\n }\n }\n }\n]}"
def restoreAccess(userName):
try:
paginator = iamClient.get_paginator('list_access_keys')
keysExist = False
keyUsage = []
for accessKey in paginator.paginate(UserName=userName):
keysExist = True
lastUsed = iamClient.get_access_key_last_used(
AccessKeyId = accessKey["AccessKeyMetadata"][0]["AccessKeyId"]
)
keyUsage.append({ "AccessKeyId": accessKey["AccessKeyMetadata"][0]["AccessKeyId"], "AccessKeyLastUsed": (time.mktime(lastUsed["AccessKeyLastUsed"]["LastUsedDate"].timetuple())) if "LastUsedDate" in lastUsed["AccessKeyLastUsed"].keys() else 9999999999999 })
if keysExist:
keyUsage.sort(key = lambda x: x["AccessKeyLastUsed"])
iamClient.update_access_key (
AccessKeyId=keyUsage[0]["AccessKeyId"],
Status='Active',
UserName=userName
)
response = iamClient.detach_user_policy(
UserName=userName,
PolicyArn=os.environ["POLICY_ARN"]
)
except ClientError as error:
if error.response["Error"]["Code"] == "NoSuchEntity":
restoreAccessByInlinePolicy(userName)
else:
raise error
def restoreAccessByInlinePolicy(userName):
try:
response = iamClient.delete_user_policy(
UserName=userName,
PolicyName="DenyAllAccess"
)
except ClientError as error:
raise error
def revokeAccess(userName):
try:
response = iamClient.put_user_policy(
UserName=userName,
PolicyName="AwsRevokeOlderSessions",
PolicyDocument=awsRevokeOlderSessionsPolicyDocument
)
paginator = iamClient.get_paginator('list_access_keys')
for accessKey in paginator.paginate(UserName=userName):
logger.info(str(accessKey))
if accessKey["AccessKeyMetadata"][0]["Status"] == 'Active':
iamClient.update_access_key (
AccessKeyId=accessKey["AccessKeyMetadata"][0]["AccessKeyId"],
Status='Inactive',
UserName=userName
)
response = iamClient.attach_user_policy(
UserName=userName,
PolicyArn=os.environ["POLICY_ARN"]
)
except ClientError as error:
if error.response["Error"]["Code"] == "LimitExceeded":
revokeAccessByInlinePolicy(userName)
else:
raise error
def revokeAccessByInlinePolicy(userName):
try:
response = iamClient.put_user_policy(
UserName=userName,
PolicyName="DenyAllAccess",
PolicyDocument=policyDocument
)
except ClientError as error:
raise error
def getLockStatus(token, api_uri, api_path, event):
responseDict = {}
try:
headers_dict = {"x-nl3-authorization-token": token}
responseIPInfo = {}
location = ""
if "." in event["detail"]["sourceIPAddress"] or ":" in event["detail"]["sourceIPAddress"]:
responseIPInfo = requests.get("https://ipinfo.io/" + event["detail"]["sourceIPAddress"] + "?token=[ipinfo_token]").json()
if "city" in responseIPInfo:
location = responseIPInfo["city"] + ", " + responseIPInfo["region"]
data_dict = {
"userIP": event["detail"]["sourceIPAddress"],
"userDevice": event["detail"]["userAgent"],
"userLocation": location,
"integrationType": "awsiamuser",
"integrationData": responseIPInfo
}
response = requests.post("".join([api_uri,api_path]), headers=headers_dict, json=data_dict)
responseDict = response.json()
except Exception as e:
responseDict = { "message": str(e) }
return responseDict
def lambda_handler(event, context):
userName = event["detail"]["userIdentity"]["userName"]
claims = {
"iss": os.environ["APP_URI"],
"iat": (datetime.utcnow().timestamp() + (-1 * 60)),
"exp": (datetime.utcnow().timestamp() + (5 * 60)),
"aud": os.environ["API_URI"],
"sub": userName
}
### Ideally the Signing Key would be stored and retrieved from a secrets manager
### and not an environmental variable
decodedDomainToken = base64.b64decode(os.environ["SIGNING_KEY"]);
token = jwt.encode(
payload=claims,
key=decodedDomainToken
)
response = getLockStatus(token, os.environ["API_URI"], os.environ["API_PATH"], event)
if response.get("locked", False):
revokeAccess(userName)
else:
restoreAccess(userName)
- If you use IAM Roles and not IAM Users, please contact support to discuss the options for implementation and specific design considerations that may be required to implement this correctly using roles.
The next step is to setup the Amazon EventBridge event rule that triggers the Lambda function for specific events. Here is a sample rule:
{"source":["aws.signin"],"detail-type":["AWS Console Sign In via CloudTrail"],"detail":{"userIdentity":{"type":["IAMUser"],"userName":["list","of","usernames","can","omit","this","line","if","all","users"]},"eventSource":["signin.amazonaws.com"],"eventName":["ConsoleLogin"],"responseElements":{"ConsoleLogin":["Success"]}}}
References:
https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-get-started.html
The next step is to setup the Amazon EventBridge event rule that triggers the Lambda function for specific events. Here is a sample rule:
References:
https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-get-started.html