OTP authentication for Microsoft Active Directory Federation Service (ADFS). It enables ADFS servers to provide multi-factor authentication (MFA) using a Time-Based One-Time Password (TOTP) Algorithm based on RFC6238. Using this MFA provider, users must enter a one-time passcode to complete a second-factor authentication login process. OTP code is delivered via 3rd party provider’s API Gateway endpoint using HTTP POST. Managed API Gateway service is provided by vendors like Amazon (AWS SNS), Microsoft (Azure API Management) etc.


Unlicensed version

  • OTP passcodes for 24 user accounts

  • OTP codes delivery via 3rd party provider’s API endpoint (Message delivery with: SMS, E-MAIL, Phone etc.)

  • OTP user accounts deactivation

  • Authentication against API endpoint

  • Send API parameters in a message body

  • API Custom AD attributes in POST message

  • Logs in Windows Applications Log

  • ADFS 2016 / ADFS 2019 / ADFS 2022 support

  • Proxy configuration

  • Support of ADFS CSS themes

Licensed version (additional features)

  • OTP passcodes for unlimited user accounts

  • OTP data storage in MS SQL service

  • OTP data storage in MS Active Directory attributes

  • OTP account lockout

  • Customization for POST data values when sending into API endpoint

  • QR code encryption with AES 256-bit encryption

  • User interface customizations

  • Free version notes are removed


Deploy SecureMFA API OTP Provider into ADFS Farm

Preparation steps

Before you can start registering “SecureMfaApiOtpProvider” into your ADFS farm you must complete bellow steps. All commands must be executed in elevated PowerShell (PS) command prompt.

1) Deploy latest “SecureMfaApiOtpprovider” PowerShell module from Microsoft PSGallery using bellow PS command:

Install-Module -Name SecureMFA_API_OTP -Repository PSGallery -Scope AllUsers

NOTE: As of April 2020, the PowerShell Gallery no longer supports lower than 1.2 TLS protocol. Hence if your servers don’t have GPO changes to reflect this requirement you may need manually to enforce TLS 1.2 for PowerShell session by using bellow command

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

If your ADFS server doesn’t have access to the Internet you can pull PowerShell module from Windows client which has Internet access using bellow PS command:

Find-Module -Name "SecureMFA_API_OTP" -Repository "PSGallery" | Save-Module -Path "C:\"

Copy C:\SecureMFA_API_OTP folder from client’s computer into ADFS server “C:\Program Files\WindowsPowerShell\Modules\SecureMFA_API_OTP”

As alternative you can download “SecureMFA_API_OTP” nupkg file manually from website. Rename nupkg file’s extension into ZIP. Unzip content into a folder “SecureMFA_API_OTP” and place it into PS Modules default location on the server. That will work the same way as pulling package with native windows PS Tools.

2) Within “C:\Program Files\WindowsPowerShell\Modules\SecureMFA_API_OTP ” directory you will find “sql_Create_Database_SecureMfaOTP.txt” Modify FILENAME location to reflect your sql server storage configuration. Open the script in SQL manager and execute it. This will create a new SQL database for “SecureMfaApiOtpProvider”

3) Within “C:\Program Files\WindowsPowerShell\Modules\SecureMFA_API_OTP ” directory update “SecureMfaApiOtpProvider.json” file.

If you are using a free version you only need to modify "sqlserver" server settings and configure your “api_endpoint”. If you will buy a license for unlimited users you will need to update "company" , "serialkey" and "subscriptionid" information to unlock licensed provider features.

If you are not running your ADFS servers using service account and you cannot use SQL integrated security to access database, you need to change: "sqlintegratedsecurity": "false“ and update "sqluseraccount" and "sqluserpassword" with relevant information.

4) If you need to generate verbose logs in windows events for troubleshooting reasons change verboselog value from “false” to “true”. Please note that verbose logging can affect your servers’ performance, use it only for troubleshooting reasons. Don’t enable “verboselog” in production environments as it may reveal configuration secrets

5) Using SQL manager provision dbo access to “SecureMfaOTP” database for ADFS service account or SQL user.

Below is a sample of a SecureMfaApiOtpProvider.json file


"company": "MyCompany",

"serialkey": "m00000000",

"subscriptionid": "1000000000000000000000001",

"sqlserver": "asqlaol1.adatum.labnet,1433",

"sqldbname": "SecureMfaOTP",

"sqlintegratedsecurity": "true",

"sqluseraccount": "",

"sqluserpassword": "",

"api_endpoint": "",

"api_timeout": "5000",

"api_code_text": "securemfa api otp code:",

"api_auth_endpoint_enabled": "false",

"api_headers_key": "x-api-key",

"api_headers_value": "V8UK9mBCgL6ztXtiJUurL3Fhn5Xjbejy1ZAhwokT",

"api_allparams_inbody": "true",

"api_customparams_enable": "false",

"api_customparams_msid": "mobile",

"api_customparams_naming": "msDS-cloudExtensionAttribute10",

"api_customparams_login": "msDS-cloudExtensionAttribute11",

"api_customparams_password": "msDS-cloudExtensionAttribute12",

"data_encryption": "false",

"data_encryption_passphrase": "d9GhT=7=Ox8-+LaZ",

"ui_customization": "false",

"ui_login_text": "",

"ui_login_failures": "0",

"ui_lockout_minutes": "5",

"totp_customization": "false",

"totp_validity_seconds": "30",

"totp_allow_reuse": "false",

"proxy_enable": "false",

"proxy_server": "proxy.adatum.labnet",

"proxy_port": "8080",

"auth_mode": "SQL",

"verboselog": "false"


Any configuration changes in json configuration file requires Install-SecureMfaApiOtpProvider to be executed again.

SecureMFA API OTP Provider Installation

Before a SecureMfaEmailOtpprovider will be invoked by AD FS, it must be registered in the system. with PowerShell command which performs the necessary installation actions including installation in the GAC, and registration in AD FS farm.

Primary ADFS node

Bellow command will install OTP authentication provider on the MAIN ADFS node.


Other ADFS node(s)

Bellow command will install OTP authentication provider on OTHER ADFS node(s).

Install-SecureMfaApiOtpProvider -NotMainNode

NOTE: If you are using federation server farm that uses Windows Internal Database, you must start installation using the primary federation server of the farm as a MAIN node. Installation needs to be executed on ADFS farm server (not web application proxy servers).


To verify if “SecureMFA Email OTP Provider” has been installed successfully.

1) Open the AD FS Management Snap-in (from Server Manager Tools menu)

2) Click Authentication Policies at left

3) In the center pane, under Multi-Factor Authentication, click the blue Edit link to the right of Global Settings.

Under Select additional authentication methods at the bottom of the page, check if “Email Time Based OTP Authentication” is selected.


When user will be prompted for multifactor authentication via ADFS interface, to receive authentication OTP code via API interface user will have to click “Get Authorization Code”

If API endpoint has a valid configuration for the user below information will be presented on successful POST message of OTP code.

If API endpoint was not setup for the user below information will be presented on successful POST message of OTP code. More details will be logged in Windows Application log.

OTP code is delivered via 3rd party provider’s API Gateway endpoint using HTTP POST. Managed API Gateway service is provided by vendors like Amazon (AWS SNS), Microsoft (Azure Api Management) etc. These Services support message deliveries via SMS, E-MAIL, Mobile Notification Network, Phone Call etc.

SecureMFA_API_OTP Provider will post OTP code to API Gateway with extra text which can be customised. Provider will return HTTP Response value for successful POST (HTTP 200) and access denied POST (HTTP 403) messages in UI when sending messages to API endpoint. All other HTTP errors will return HTTP status code value only.

Information is updated in SQL Database or in Microsoft Active Directory attributes after user’s successful logon. What data store will be used depends on authentication mode configuration in MFA provider’s configuration file.

User Lockouts

This feature only works for licensed adapters. If you set “login_failures” to be more than a zero in SecureMFA data store you will see failed user authentication attempts. When user reaches “failedlogoncount” number of attempts set in “login_failures” value user’s account will be locked out for a period of time set in "lockout_minutes" . If you want to disable this feature you must set “login_failures” to zero. All values are configured in SecureMfaApiOtpProvider.json file.

SecureMfaApiOtpProvider.json config settings to enable 5 min lockouts for 15 failed OTP passcode attemps :

"login_failures": "15"

"lockout_minutes" : "5"

Disable User

This feature allows to disable SecureMFA OTP account and immedicably restrict MFA authentication without disabling user in Active Directory.

User will be getting following message If user’s SecureMFA OTP account has been disabled by support team.

How to disable SecureMFA OTP accounts please read “SecureMFA Support Tools” documentation.

Support tools are packaged into a separate PowerShell module which is used for OTP support and maintenance tasks.


Encryption only works for licensed adapters. AES 256-bit encryption is configured with the .NET wrapper side by using public sealed class AesCng: System.Security.Cryptography.Aes. It provides a Cryptography Next Generation (CNG) implementation of the Advanced Encryption Standard (AES) algorithm and allows to run provider when FIPS compliant algorithms for encryption are enforced on the Windows platform. The cipher mode is Cipher Block Chaining (CBC). The passphrase can be configured in the configuration file and it is recommended to be between 16-18 random characters. It is salted with 16 bytes string, zero padding and 4 key iterations. Full documentation on “AesCng .NET class” can be found in Microsoft documentation (

SecureMfaApiOtpProvider.json config settings to enable encryption:

"data_encryption": "true"

"encryption_passphrase": "d9GhT=7=Ox8-+LaZ"

To enforce Windows Operating System cryptography to use FIPS compliant algorithms for encryption, hashing, and signing run bellow command in elevated PowerShell (PS) o a server. More details on the policy can be found by following link: System cryptography: Use FIPS compliant algorithms for encryption.

New-ItemProperty -path 'HKLM:\System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy' -name 'Enabled' -value 1 -PropertyType 'DWord' -Force; Restart-Computer -Force;

When it is enabled your secret code is encrypted in Database and it looks like bellow.

Theme Customizations

Licensed clients can customize adapter text which is presented to the users during logon. You can use some simple html code like links to provide users with self-service portal links etc. Text customizations are configured in “SecureMfaApiOtpProvider.json” file.

NOTE: "ui_customization" must be set to "true" for bellow changes to take effect.

"ui_login_text": "Enter the passcode generated by your authenticator


All successful second factor authentication sessions will issue a new Actual Authentication method value:

With URI:

Enable OTP Data Store in Microsoft Active Directory Attributes

This feature is only available for licensed OTP providers. It allows to store User OTP attributes in Active Directory domain. This removes dependency and cost of having MS SQL service configuration to store OTP database. To enable this feature, you have to update SecureMfaApiOtpProvider.json file to enable AD mode and update encryption passphrase with your custom string which will be used to encrypt secret data in AD attribute. AD mode doesn’t support unencrypted secrets.

"auth_mode": "AD"

"data_encryption_passphrase": "d9GhT=7=Ox8-+LaZ"

You will have to execute following script “Create_SecureMFA_API_OTP _AD_attributes.ps1” which comes with OTP provider on Read-Write domain controller. You need to execute this script on Read-Write domain controller with AD account which is member of Schema Admins group. SecureMFA OTP provider when operates in AD mode it requires custom Active Directory (AD) attributes to be created to store OTP data for the user. This action cannot be undone and needs to be tested in your TEST domain first before moving into PRODUCTION. New AD Schema Attributes will be added into custom SecureMFA Auxiliary Class and that Class will be added into Existing User Class as AD Schema best practices suggest. OID numbers for custom attributes are from SecureMFA Private Enterprise range assigned by . Which do not overlap with other vendors OIDs numbers used to create custom AD attributes.

Note: THIS ONLY REQUIRES IF YOU PLAN TO RUN SecureMFA API OTP Provider in AD MODE. By default, write access to a new Active Directory attributes is allowed only for domain administrators. If you run ADFS under dedicated service account please make sure it has WRITE access to “sMFA-* user attributes”, in order for the provider to be able to update Active Directory user info.

Bellow print screen shows how AD user’s custom attributes will look like.

Following are AWS CLI bash commands to setup API Gateway interface.

1. Create SNS topic.

TOPIC_ARN=$(aws sns create-topic --name 'test1test1' --output text --query 'TopicArn' --region eu-west-1)

2. Verify ARN value for SNS topic


3. Create the subscription, substituting your own SMS device or email address in the SUBSCRIPTION_ENDPOINT variable. Make sure you include the country code in your number. You may need to confirm your subscription by clicking a link which you will receive from AWS Notification service if it is email endpoint.



aws sns subscribe --topic-arn $TOPIC_ARN --protocol sms --notification-endpoint $SUBSCRIPTION_ENDPOINT --region eu-west-1



aws sns subscribe --topic-arn $TOPIC_ARN --protocol email --notification-endpoint $SUBSCRIPTION_ENDPOINT --region eu-west-1

4. Verify if subscription is active

aws sns list-subscriptions-by-topic --topic-arn $TOPIC_ARN --region eu-west-1

5. Confirm that your subscription is configured correctly by publishing a message to your SNS topic. You should receive a text on your mobile device or email.

aws sns publish --topic-arn $TOPIC_ARN --message 'API test message' --region eu-west-1

Now that we have an SNS topic with a subscription that sends to our endpoint device, next step is to set up API Gateway with a service proxy.

6. Create a REST API in API Gateway

API_ID=$(aws apigateway create-rest-api --name 'API Proxy' --output text --query 'id')

7. Fetch your resources from API Gateway

aws apigateway get-resources --rest-api-id $API_ID

8. Store the value of that resource id into a ROOT_RESOURCE_ID variable

ROOT_RESOURCE_ID=$(aws apigateway get-resources --rest-api-id $API_ID --output text --query 'items[0].id')

9. create API /ingest resource. This resource should be created for each user which will use SecureMFA API OTP provider to authenticate.

INGEST_RESOURCE_ID=$(aws apigateway create-resource --rest-api-id $API_ID --parent-id $ROOT_RESOURCE_ID --path-part test1test1 --output text --query 'id')

10. See that there are two resources in our REST API now. Each extra user will need a new resource there.

aws apigateway get-resources --rest-api-id $API_ID

11. Wire up a method for our resource. We want clients to send us data to be ingested into our topic, so we’ll use a POST method. Each extra user will need this config.

aws apigateway put-method --rest-api-id $API_ID --resource-id $INGEST_RESOURCE_ID --http-method POST --authorization-type NONE

Now created the basic elements of our API Gateway REST API, in addition to the SNS topic and subscription we created in the previous section. Next, connect our two pieces by creating a service proxy integration. Creation of role is one time configuration.

12. Create the role with the proper trust policy

ROLE_ARN=$(aws iam create-role \

--role-name service-proxy-role \

--assume-role-policy-document '{

"Version": "2012-10-17",

"Statement": {

"Effect": "Allow",

"Principal": {"Service": ""},

"Action": "sts:AssumeRole"


}' \

--output text \

--query 'Role.Arn')

13. Update the inline policy on the role. With an IAM role, you grant it permission to perform certain actions. In this instance, we want to grant it permission to publish messages to our SNS topic. Resource value should include multiple values if you will have multiple user subscriptions.

aws iam put-role-policy \

--role-name service-proxy-role \

--policy-name 'sns-publish' \

--policy-document '{

"Version": "2012-10-17",

"Statement": {

"Effect": "Allow",

"Action": "sns:Publish",

"Resource": "'$TOPIC_ARN'"



14. Verify all resources assigned to gservice-proxy-role

aws iam get-role-policy --role-name 'service-proxy-role' --policy-name 'sns-publish'

15. Create the integration between our HTTP endpoint and our AWS service

REGION=$(aws configure get region)

aws apigateway put-integration \

--rest-api-id $API_ID \

--resource-id $INGEST_RESOURCE_ID \

--http-method POST \

--type AWS \

--integration-http-method POST \

--uri 'arn:aws:apigateway:'$REGION':sns:path//' \

--credentials $ROLE_ARN \

--request-parameters '{

"integration.request.header.Content-Type": "'\'application/x-www-form-urlencoded\''"

}' \

--request-templates '{

"application/json": "Action=Publish&TopicArn=$util.urlEncode('\'$TOPIC_ARN\'')&Message=$util.urlEncode($input.body)"

}' \

--passthrough-behavior NEVER

16. Add your integration response

aws apigateway put-integration-response \

--rest-api-id $API_ID \

--resource-id $INGEST_RESOURCE_ID \

--http-method POST \

--status-code 200 \

--selection-pattern "" \

--response-templates '{"application/json": "{\"body\": \"Message received.\"}"}'

17. Add a method response. This is how API Gateway takes the integration response and assembles it into an HTTP-compatible response

aws apigateway put-method-response \

--rest-api-id $API_ID \

--resource-id $INGEST_RESOURCE_ID \

--http-method POST \

--status-code 200 \

--response-models '{"application/json": "Empty" }'

18. Before API can be used, deploy it.

aws apigateway create-deployment --rest-api-id $API_ID --stage-name prod

19. Use curl for invoking API endpoint to test functionallity.

curl -X POST https://$API_ID.execute-api.$ --data 'API test message 2' -H 'Content-Type: application/json'

If message is received via API Gateway using curl command, you can use it with SecureMFA-API-OTP Provider. Please see for more details in ‘Enable API Endpoint’ section.

Enable API Endpoint

Endpoint for posting OTP codes into API Gateway is specified under "api_endpoint" value. NOTE, API endpoint is accessed by ADFS Servers (not web application proxy servers). Most likely ADFS servers will need to access API endpoint via corporate proxy if it is outside corporate network. To allow access via proxy server ensure "proxy_enable" is set to "true". Otherwise you’ll have to set system settings for default proxy settings.

Below is a sample of json configuration file which will work with Amazon AWS API Gateway service It has time out value (ms) to terminate TCP sessions which take long time to complete and code text value which is extra text added to POST message into API gateway:

"api_endpoint": "",

"api_timeout": "5000",

"api_code_text": "SecureMFA API OTP Code:"

Complete URI for API endpoint will be created by combining "api_endpoint" and user’s anchor value from ADFS to make it unique. User’s anchor value will be captured before “@” value and all special characters will be removed including “.” or “_”. Text is converted into lowercases. For example, user’s account with UPN “Test1.Test1@adatum.labnet” will be converted into “test1test1” value.

API Gateway must be configured by following a RESTful API design, the combination of resource + method which allows to expose POST actions on a user’s object. For example, making a POST request to /prod/test1test1 should trigger delivery of message which is received in a POST payload.

Below is a sample how properly formatted URI will look like:

Please see Configure “AWS API Gateway for SMS or EMAIL deliveries” section to get more details how to setup test API Gateway.


Provider support “Custom Header” authentication to authenticate against API endpoint.

Below is a sample of json configuration file which enables “Custom Header” authentication against endpoint. It includes custom header key and value combination to be included with POST message.

"api_auth_endpoint_enabled": "true",

"api_headers_key": "x-api-key",

"api_headers_value": "V3UK6mUMgL6ztXtiJUurA3Fhn5Xjbejy1ZAhwokT",

Proxy Configuration

API endpoint is accessed by ADFS Servers (not web application proxy servers). Most likely ADFS servers will need to access API endpoint via corporate proxy if it is outside corporate network. Proxy authentication is done under ADFS service account.

SecureMfaApiOtpProvider.json config settings to enable proxy:

"proxy_enable": "true",

"proxy_server": "proxy.adatum.labnet",

"proxy_port": "8080",

As alternative you can configure system settings to enforce server to send messages via corporate proxy.


Send API parameters in a message body

Provider allows to send all parameters and data for API endpoint in POST message by changing bellow setting in JSON configuration file

"api_allparams_inbody": "true"

Data for API endpoint will be included in a message body using JSON format and it will include following information:

{"upn":"test1@adatum.labnet","otp":"123456","email":"","logonip":"","useragent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0)","product":"mfa-otp-api","company":"MyCompany","comments":"OTP code is valid until 14/01/2021 16:35:00"}

You can test this functionality by using API Endpoint in Self-service password reset portal (SSPR) or you can use it with your own API solution which accepts JSON formatted messages.

API Custom AD attributes

For customised integrations API provider allows to include custom AD attributes in POST message body when using

"api_allparams_inbody": "true" setting in JSON config file. To include custom AD attributes SecureMfaApiOtpProvider.json must be enabled as below:

"api_customparams_enable": "true"

"api_customparams_msid": "mobile"

"api_customparams_naming": "msDS-cloudExtensionAttribute10"

"api_customparams_login": "msDS-cloudExtensionAttribute11"

"api_customparams_password": "msDS-cloudExtensionAttribute12"

Note: Key value in POST message will be value after “api_customparams_” and AD user attribute value can be any user object attribute name in AD which is accessible to ADFS service account.

To see data that is sent in POST message you can enable verbose logging in the provider’s JSON configuration file. POST message details will be published in the Windows Applications Log file.

"verboselog": "true"

POST Message Data Values

This feature is only available for licensed OTP providers. You can add your custom information with POST message for API endpoint. Bellow JSON configuration setting allows to send extra information with POST data to API endpoint.

"api_code_text": "My custom value(s)"

TOTP Time Drift

This feature is only available for licensed OTP providers. By default OTP time drift is allowed to be 30 seconds in the future and in the past as defined in RFC6238 , this makes provider to be compatible with other providers (SecureMfaOtp ) which allow to use QR apps on mobile device to generate OTP tokens. If your delivery method cannot deliver OTP message within default timeframe you can increase OTP validity value by adding extra seconds, but this will break compatibility with other authentication providers indicated above.

SecureMfaApiOtpProvider.json config settings to enable 60 seconds time drift:

"totp_customization": "true",

"totp_validity_seconds": "60",

"totp_allow_reuse": "false",

Note: By default, the authorization code can be used only once. Suppose you want to reuse the authorization code multiple times during the timeframe update below the config file setting.

"totp_allow_reuse": "true",


The below system events in Windows Event log may be used to verify and troubleshoot SecureMfa API OTP Provider:

Log Name: AD FS/Admin

All provider related logs are stored in Windows Application Event logs and some data in SQL table or AD Attributes.

Windows Application Event:

Source: Secure MFA API OTP

Event ID 5550: Successful Configuration Events

Event ID 5551: Failed Configuration Events

Event ID 5552: Successful Provider Events

Event ID 5553: Failed Provider Events

Event ID 5556: Successful OTP Events

Event ID 5557: Failed OTP Events


Table: secrets

[lastlogon] – Time stapm of last successful logon;

[logoncount] – Total number of successful user logons;

[failedlogoncount] – Number of failed logons in a row;

[failedlastlogon] - Time stamp of last failed logon;

[failedcode] – Last failed user’s input;

[logonip] – IP address of remote user;

[useragent] – Details of user’s agent details from last logon;

AD Attributes:

[sMFA-OTP-lastlogon] – Time stapm of last successful logon;

[sMFA-OTP-logoncount] – Total number of successful user logons;

[sMFA-OTP-failedlogoncount] – Number of failed logons in a row;

[sMFA-OTP-failedlastlogon] - Time stamp of last failed logon;

[sMFA-OTP-failedcode] – Last failed user’s input;

[sMFA-OTP-logonip] – IP address of remote user;

[sMFA-OTP-useragent] – Details of user’s agent details from last logon;


Deployment of a new version can be done by pulling latest version from PowerShell Gallery by using bellow command:

Install-Module -Name SecureMFA_API_OTP -Repository PSGallery -Scope AllUsers -Froce

You’ll need to repeating all deployment steps as it was done for original installation. If it is highlighted in deployment notes that attribute store needs to be extended for OTP data, you’ll need extra step to complete your upgrade.

· If you are using to store your user OTP data in MS SQL you may need to execute “sql_Upgrade_From_Previous_Version.txt” on your SQL database.

· If you are using to store your user OTP data in AD you may need to execute “Create_SecureMFA_API_OTP _AD_attributes.ps1” on your Domain controller.

Files can be found in a directory with other SecureMFA Provider files. This will add any extra missing columns/attributes in the Database or AD.

OTP Users Management

Support tools for OTP Users management are packaged into a separate PowerShell module which can be used for OTP accounts management tasks.

For more details please read “SecureMFA Support Tools” documentation.

Configure AWS API Gateway for SMS or EMAIL deliveries

Bellow Amazon Web Services configuration shows how to setup API Gateway which allows to deliver OTP codes from SecureMFA API Provider using SMS or EMAIL. You’ll need admin access to AWS account and bash console for AWS CLI commands to complete configuration. You must use API provider setting "api_allparams_inbody": "false" in JSON config file for bellow exsample to work. Working deployment OTP messaging flow will look like bellow