Company Portal
- User Guide

Company Portal

The Next Level3 Company Portal provides companies with the ability to manage their JIT Access application integrations, Manage Identity for Enabled Users, Monitor Authentication traffic and Manage Cyber Policies.

To access the company portal visit: company.nextlevel3.com

Registration

To access the company portal you must first register your company with Next Level3. Click the Register A New Account link to register a new company.

Because Next Level3 only allows access to its company portal via biometric authentication you must first have a FIDO2 compliant device you wish to use to authentication. During the registration process, you will be asked to setup a passkey using your Fido2 device for authentication. 

Registration will create an Admin level account user within the Next Level3 Company Portal which is the initial admin account using the username you registered with.

Sign-in

Once registered enter in your username and click signin to authenticate to the Company Portal.

Dashboard

The Next Level3 Dashboard allows you to monitor requests made for your company in real time. 

To filter the dashboard view you can edit the filters listed at the top of the page. Clicking on any item in the map will allow you to view the details for the event.

Protecting Applications

To enable Next Level3 protection for an application you must first add the application in the company portal. 

 

 
Adding Applications

To add an application, select Applications from the main menu and click Actions –> Add Application.

Give your Application Name and the URL for your application and hit Save.

*Note: Do not include a protocol prefix like http:// or https:// in front of the application URL. 

 

Identity Provider Integration

Next Level3 Supports integrations to a variety of best in class identity providers as well as native support from most development languages. The following sections outline the steps required to setup and configure the integration for each of these providers. To get started first choose the identity provider your application is using and then follow the instructions in that section to enable Next Level3 Identity for that provider.

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

Adding Next Level3 Custom Auth0 Action

The first step to add an NL3 Account Protection Check to an existing application using Auth0 for authentication is to add a custom action which will then be added to your existing login flow.

Adding an Auth0 Custom Action

a. After logging into the Auth0 management portal, navigate to ‘Actions -> Library’ from the main menu and click the “Build Custom” button to build a custom action.

b. Enter the following Information (replacing myApplication with the name of your application) in the Custom Action form and click Create.

  • Name – myApplication Account Protection Check
  • Trigger – Login/Post Login
  • Runtime – Node 16 (Recommended)

c. In the code editor for the action, remove all the existing default code and replace it with the following NL3 Action code.

            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,
        '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);
				resolve(response_body.toString());
			});
			response.on('error', (error) => {
        console.log("Error = " + error.message);
				reject(error);
			});
    });
    req.write(post_data);
    req.end();
  });
}

exports.onExecutePostLogin = async (event, api) => {
  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) {
      const res = await getLockStatus (authToken, event.secrets.API_HOST, event.secrets.API_PATH, event);
      var result = JSON.parse(res);

      if(result) {
        console.log(JSON.stringify(result));
        if(result.locked) {
          api.access.deny(event.secrets.LOCKED_MESSAGE);
        }
      } else {
        //Add logic for violations if required
      }
    }
  }
};
        

Once you have updated the code, click “Save Draft”. After doing this, your action code should look like the following:

d. Now you need to set up some secrets for the new custom action in order to configure it for your Auth0 protected application. This includes a secret for your NL3 signing key so that the action can check the lock status of users that are set up for Account Protection. You can retrieve your signing key from the Next Level3 Company Portal (https://company.nextlevel3.com) after you have added the application to the company portal and validated the associated domain for that application. 

To update secrets, click the ‘Key’ Icon to the left of the code editor called secrets and select the “Add Secret” button   

e. Enter to following secret key/value pairs one at a time in the ‘Define Secret’ form and click “Create”.

  • Key = LOCKED_MESSAGE
  • Value = Add a non-descript error message of your choosing such as ‘Login Failed’

  • Key = SIGNING_KEY
  • Value = Paste your domain key provided by the Next Level3 company portal for the domain.
  • Key = CLIENT_ID
  • Value = The Auth0 Client ID for your application which can be found under the “Applications” menu in Auth0
  • Key = API_HOST
  • Value = Currently, the API host for production endpoints is api.nextlevel3.com
  • Key API_PATH
  • Value = Currently, the API path for production is /v1/AccountProtectionCheck
  • Key APP_URI
  • Value = This must match the value of the Domain URL you configured in the NL3 company portal (e.g. myapp.domain.com)
f. Next click the package icon labeled “Modules” to add some additional npm modules to the action. The only dependency that is not included by default is “njwt”. Click the ‘Add Dependency’ button, then enter “njwt” into the “Name” field and click the “Create” button. 
 
 

g. Next, click “Save Draft” from the code editor window to save the action.

h. Click “Deploy” from the code editor window to deploy the action. After you click “Deploy”, a slider popup will appear letting you know that the action was successfully deployed.

i. Click “Add to Flow” from that slider popup. 
                 * if you missed the popup slider,  you can also navigate to “Actions -> Flows” and select the Login tile.

j. Click the “Custom” tab to list your custom actions. You should see the NL3 Account Protection action you just created in the list.

k. Drag and drop the NL3 Account Protection action into the flow.

Your login flow should look similar to this now.

l. Click “Apply” to apply the flow changes.

That’s it. You have now enabled Next Level3 Account Protection. After completing the second phase of integration (User Enablement) and enabling users,  you will be able to test out the lock/unlock capabilities in your site for users that have enabled their accounts for protection.

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-cli-csharp?tabs=azure-cli%2Cin-process

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:

Microsoft Azure AD and 0365 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

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

https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/deployment/how-to-connect-fed-azure-adfs

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.
  • Example
  • 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.

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”.
  • 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:

https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-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.

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

ADDING ACCOUNT PROTECTION TO AMAZON COGNITO

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:

 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

*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

Native Language Integrations

Next Level3 Supports integration directly in multiple native languages. To integrate Cloud Identity into your application refer to the following: examples.

Node JS Integration

The Next Level3 Node.js integration is designed to be used for your existing applications or sites that are using native Node.js code for authentication. This integration will allow you to easily add Account Protection to any application that leverages Node.js for authentication. 

Pre-requisites

  • Node.js Application
  • Next Level3 Company Account
  • Signing Key created for an application in the Next Level3 Company Portal

The following Node.js code sample can be used to integrate an account protection check into your existing authentication flow for custom Node.js applications that are handling authentication within the application or where a third-party identity provider does not have a supported integration:

            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, // UserId for which to check lock status
  };
  /* Signing Key would ideally 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) {
        // Code to deny login
      }
    } else {
      failed = true;
    }
  } else {
    failed = true;
  }
  if (failed) {
    if (process.env.FAIL_CLOSED == "true") {
      // Code to deny login
    }
  } else {
    // Code to allow login
  }
};
        

Python Integration

The Next Level3 Python integration is designed to be used for your existing applications or sites that are using Python for authentication. This integration will allow you to easily add Account Protection to any application that leverages Python for authentication. 

 

Pre-requisites

  • Python Application
  • Next Level3 Company Account
  • Signing Key created for an application in the Next Level3 Company Portal

The following Python code sample can be used to integrate an account protection check into your existing authentication flow for custom Python applications that are handling authentication within the application or where a third-party identity provider does not have a supported integration:

            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 protectionCheck (userName, validationData):
  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"], validationData)
  if response.get("locked", False):
    // Code for prohibiting login and returning generic error message

  // Code for unlocked or unprotected accounts
        

.Net Integration

Microsoft .NET Logo PNG Vector (AI) Free DownloadThe following example shows how it’s possible to perform a PreAuthentication Check with Next Level3 to determine lock status and initiate MFA Push based unlocking. If the status is locked, then a push unlock request is sent to the user’s registered devices.

This code can be used in any application prior to authentication with Active Directory or another IDP.

            using Microsoft.VisualBasic.FileIO;
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Microsoft.IdentityServer.Public;
using System.Security.Claims;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System.IdentityModel.Tokens.Jwt;
using System.IdentityModel;
using Microsoft.IdentityModel.Tokens;
using System.Web;

namespace NL3Sample
{
    /// <summary>
    /// IPConverter is a class for fixing the serialization of NetworkLocation objects
    /// </summary>
    //public class IPConverter : JsonConverter<IPAddress>
    public class IPConverter : JsonConverter<IPAddress>
    {
        public override void WriteJson(JsonWriter writer, IPAddress value, JsonSerializer serializer)
        {
            writer.WriteValue(value.ToString());
        }

        public override IPAddress ReadJson(JsonReader reader, Type objectType, IPAddress existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            var s = (string)reader.Value;
            return IPAddress.Parse(s);
        }
    }

    public class NL3SampleClass
    {

        //Get Signing Key from Secret Storage
        private string GetSigningKey(String clientId)
        {
            //Add logic to get signing key based on cliendId or the ID for the application
            return signingKey;
        }

        /// <summary>
        /// This method checks the lock status in NL3 for the user that is authenticating.
        /// </summary>
        /// <param name="requestContext"></param>
        /// <param name="securityContext"></param>
        /// <param name="protocolContext"></param>
        /// <param name="additionalClaims"></param>
        /// <returns></returns>
        public EvaluatePreAuthentication(RequestContext requestContext, SecurityContext securityContext, ProtocolContext protocolContext, IList<Claim> additionalClaims)
        {
            //The parameters passed in are default parameters passed by ADFS
            //You may have access to this informaiton in other objects
            var referer = requestContext.Headers.Get("Referer");
            Uri refererUri = new Uri(referer);
            string clientId = protocolContext.ClientId;
            if(clientId == null || clientId.Length == 0)
            {
                clientId = HttpUtility.ParseQueryString(refererUri.Query).Get("client_id");
            }
            // Get Base64 Encoded Signing Key
            string base64SigningKey = GetSigningKey(clientId);
            if (base64SigningKey != null && base64SigningKey.Length > 5)
            {
                //Get current username
                string username = securityContext.UserIdentifier;

                //Decode the signing key
                byte[] key = Convert.FromBase64String(base64SigningKey);
                SymmetricSecurityKey securityKey = new SymmetricSecurityKey(key);

                //Create JWT passing in the FQDN associated with your signing key as first option
                JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
                JwtSecurityToken token = handler.CreateJwtSecurityToken("<FQDN_FOR_SIGNING_KEY>", "api.nextlevel3.com", new ClaimsIdentity(new[] {
                      new Claim("sub", username)}), DateTime.UtcNow.AddMinutes(-1), DateTime.UtcNow.AddMinutes(5), DateTime.UtcNow.AddMinutes(-1), new SigningCredentials(securityKey,
                    SecurityAlgorithms.HmacSha256Signature));

                //If no access to requestContext, get current client's IP and other details some other way
                HttpWebRequest requestIPDetails = (HttpWebRequest)WebRequest.Create("https://ipinfo.io/" + requestContext.ClientIpAddresses[0].ToString() + "/json?token=<API_TOKEN>");
                JObject jsonIPDetails = new JObject();
                string location = "";
                sting geo = "";
                using (HttpWebResponse responseIPDetails = (HttpWebResponse)requestIPDetails.GetResponse())
                using (Stream stream = responseIPDetails.GetResponseStream())
                using (StreamReader reader = new StreamReader(stream))
                {
                    jsonIPDetails = JObject.Parse(reader.ReadToEnd());
                    if(jsonIPDetails.HasValues)
                    {
                        location = jsonIPDetails["city"] + ", " + jsonIPDetails["region"];
                        geo = jsonIPDetails["loc"];
                    }
                }
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://api.nextlevel3.com/nl3/api/v1/accountProtectionCheck");
                var jsonSettings = new JsonSerializerSettings();
                jsonSettings.Converters.Add(new IPConverter());
                JObject jPostData = new JObject
                {
                    ["userIP"] = requestContext.ClientIpAddresses[0].ToString(),
                    ["userDevice"] = requestContext.UserAgentString,
                    ["userLocation"] = location,
                    ["userGeo"] = geo,
                    ["integrationType"] = ".NET"
                };
                jPostData.Add(new JProperty("integrationData", JObject.Parse(JsonConvert.SerializeObject(requestContext, Formatting.Indented, jsonSettings))));
                jPostData["integrationData"]["locationInfo"] = jsonIPDetails;
                byte[] byteArray = Encoding.UTF8.GetBytes(jPostData.ToString());
                request.Method = "POST";
                request.ContentType = "application/json";
                request.ContentLength = byteArray.Length;
                request.Headers.Add("x-nl3-authorization-token", handler.WriteToken(token));
                request.Headers.Add("x-forwarded-for", requestContext.ClientIpAddresses[0].ToString());
                request.Headers.Add("User-Agent", requestContext.UserAgentString);
                request.Headers.Add("x-nl3-device-location", geo);
                request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
                Stream requestStream = request.GetRequestStream();
                requestStream.Write(byteArray, 0, byteArray.Length);
                requestStream.Close();

                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                using (Stream stream = response.GetResponseStream())
                using (StreamReader reader = new StreamReader(stream))
                {
                    JObject jsonResponse = JObject.Parse(reader.ReadToEnd());
                    JToken locked;
                    if (jsonResponse.TryGetValue("locked", out locked))
                    {
                        if (locked.Value<bool>())
                        {
                            // code to block login
                        }
                        else
                        {
                            // code to allow login
                        }
                    }
                    else
                    {
                        // code if user not enabled for NL3 protection
                    }
                }
            }

            // Code to fail open or closed based on missing configuration or other issues
        }
    }
}

        

Protecting Endpoints

Windows

Prerequisites

 
  • Access to the NextLevel3 portal to an existing application with an API key.
  • Access as ‘administrator’ on the system that the module will be installed on
  • Users available for testing.

Standard Installation

  1. Log into your NextLevel3 portal at https://company.nextlevel3.com/
  2. Navigate to Applications and choose the appropriate application (in this case jump-servers).
  3. Copy the application URL (in this case jump-servers.infra.company.com) somewhere you can retrieve it.
 
4. Next, navigate to Keys & Tokens, select the Application from step 2 and Key Type “Application Signing Keys”, then click on the Key to retrieve the Application Signing Key.
 
5. Copy the signing key (it may be multiple lines), and store it with the URL from earlier.
 
6. Download the installer from the company portal on the Windows user account you are protecting.
 
7. Copy the file to the following directory: C:\Windows\System32
 
8. Run the following Windows Powershell commands to create the LSA Registry entries to install sub authentication module, configure event logging, and create registry entries for sub authentication module connection to NextLevel3:
 
 
9. Depending on the endpoint application you are configuring, you will need to add the API Key, API URI, and APP URI to the registry using the following commands:
 
 
10. Restart your device, this will install the module and its configuration. This will require your account to use NextLevel3 MFA, and, if the NextLevel3 server cannot be contacted, will be denied access..
 
11. Confirm that you and other users can log in, especially that the appropriate connectivity and usernames are in place.
 

Removal

The module consists of Item’s and ItemProperty’s that were created during the NextLevel3 installation. You will need to run the following command to remove the module from your device.

After running the above commands you will need to restart your device for the changes to take place.

Additional Configuration

There following command can be entered to give designated users NextLevel3 MFA bypass privileges:

 

 
 

Linux

Prerequisites

You will need:

  • Access to the NextLevel3 portal to an existing application with an API key.
  • Access as ‘root’ (directly or via su/sudo) on the system that the module will be installed on.
  • Users available for testing.

Standard Installation

  1. Log into your NextLevel3 portal at https://company.nextlevel3.com/
  2. Navigate to Applications and choose the appropriate application (in this case jump-servers).
  3. Copy the application URL (in this case jump-servers.infra.company.com) somewhere you can retrieve it.
 
4. Next, navigate to Keys & Tokens, select the Application from step 2 and Key Type “Application Signing Keys”, then click on the Key to retrieve the Application Signing Key.