Protect workflows with MFA
The OneLogin Multi-Factor Authentication (MFA) API enrolls user devices, triggers push or sms notifications, and verifies one time passwords (OTP).
This API is useful to step up authentication to address specific login risks and to provide additional security for any process or workflow.
The following example demonstrates how to discover a user’s enrolled auth factors and how to verify a token from an auth factor. (Note: Replace <subdomain> with your OneLogin subdomain.)
1. Get a list of enrolled devices
Prior to verifying a token, determine the factors enrolled for the user. Users often enroll devices during the onboarding process. The API can also be used to enroll devices, but we wont cover that is this example.
API Reference: Get Enrolled Factors
- Node.js
- Dotnet
- Ruby
- Python
- Java
const request = require("request")
const subdomain = '<subdomain>'
let accessToken = '763f048afe9eaa5bbf0356dfabad0164be793dba6a0f04804cecf83ea95f5ad9'
let userId = 35510511
let options = {
method: 'GET',
uri: `https://${subdomain}.onelogin.com/api/1/users/${userId}/otp_devices`,
auth: {
bearer: accessToken,
}
}
request(options, function(error, response, body){
console.log(body)
})
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var subdomain = "<subdomain>";
var userId = 35510511;
var uri = String.Format("https://{0}.onelogin.com/api/1/users/{1}/otp_devices",
subdomain, userId);
var request = new HttpRequestMessage(){
Method = HttpMethod.Get,
RequestUri = new Uri(uri)
};
var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
dynamic json = JsonConvert.DeserializeObject(responseBody);
require 'httparty'
subdomain = '<subdomain>'
user_id = 35510511
access_token = '763f048afe9eaa5bbf0356dfabad0164be793dba6a0f04804cecf83ea95f5ad9'
response = HTTParty.get("https://#{subdomain}.onelogin.com/api/1/users/#{user_id}/otp_devices",
headers: {
'Authorization' => "Bearer #{access_token}"
}
)
puts response
import requests
subdomain = '<subdomain>'
access_token = 'e00a5f82c0c33170bb455b051e9870f7cb03cf25f3dede395fc1674818c70bd3'
user_id = 35510511
uri = 'https://{}.onelogin.com/api/1/users/{}/otp_devices'.format(subdomain, user_id)
r = requests.get(uri,
headers = {
'Authorization': 'Bearer {}'.format(access_token)
}
)
response = r.json()
print(response)
CloseableHttpClient client = HttpClientBuilder.create().build();
String subdomain = "<subdomain>";
String accessToken = "50266a3de33d45317563b64c1c0bd0ea6c403acd3cff5f57d4cfaf71659a9dc9";
Integer userId = 35510511;
String uri = String.format("https://%s.onelogin.com/api/1/users/%s/otp_devices", subdomain, userId);
HttpGet request = new HttpGet(uri);
request.setHeader("Authorization", String.format("Bearer %s", accessToken));
try {
CloseableHttpResponse reponse = client.execute(request);
String content = EntityUtils.toString(reponse.getEntity());
JSONObject json = new JSONObject(content);
System.out.println(json);
} catch (IOException e) {
e.printStackTrace();
}
Response
{
"status": {
"type": "success",
"code": 200,
"message": "Success",
"error": false
},
"data": {
"otp_devices": [
{
"needs_trigger": true,
"default": false,
"active": true,
"auth_factor_name": "OneLogin Protect",
"type_display_name": "OneLogin Protect",
"user_display_name": "OneLogin Protect",
"id": 525509
},
{
"needs_trigger": false,
"default": false,
"active": true,
"auth_factor_name": "Google Authenticator",
"type_display_name": "Google Authenticator",
"user_display_name": "Google Authenticator",
"id": 526551
}
]
}
}
2. Send a push notification
We learned in step 1 that the user has OneLogin Protect enrolled as an authentication factor. Consult the needs_trigger attribute to verify if the factor is activated. Tokens cannot be verified until the factor is activated.
OneLogin Protect triggers a push notification to the users mobile device. Once the user receives the notification, the Protect app opens and the user must Accept or Deny the action.
API Reference: Activate a Factor
Not all factors require this step. For example, use Google Authenticator to capture the user’s token. Since the needs_trigger attribute is false, you can proceed to step 3.
- Node.js
- Dotnet
- Ruby
- Python
- Java
const request = require('request')
const subdomain = '<subdomain>'
let accessToken = 'f38c26c49fe6550e9927806918ccb270ed34a2afe9fe63ef6b18973617ac0c5a'
let userId = 35510511
let deviceId = 525509
let options = {
method: 'POST',
uri: `https://${subdomain}.onelogin.com/api/1/users/${userId}/otp_devices/${deviceId}/trigger`,
auth: {
bearer: accessToken
},
headers: {
'Content-Type': 'application/json'
}
}
request(options, function(error, response, body){
console.log(body)
})
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var subdomain = "<subdomain>";
var userId = 35510511;
var deviceId = 525509;
var uri = String.Format("https://{0}.onelogin.com/api/1/users/{1}/otp_devices/{2}/trigger",
subdomain, userId, deviceId);
var request = new HttpRequestMessage(){
Method = HttpMethod.Post,
RequestUri = new Uri(uri),
Content = new StringContent("")
};
// We add the Content-Type Header like this because otherwise dotnet
// adds the utf-8 charset extension to it which is not compatible with OneLogin
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
dynamic json = JsonConvert.DeserializeObject(responseBody);
require 'httparty'
subdomain = '<subdomain>'
access_token = 'e00a5f82c0c33170bb455b051e9870f7cb03cf25f3dede395fc1674818c70bd3'
user_id = 35510511
device_id = 525509
response = HTTParty.post("https://#{subdomain}.onelogin.com/api/1/users/#{user_id}/otp_devices/#{device_id}/trigger",
headers: {
'Authorization' => "Bearer #{access_token}",
'Content-Type' => 'application/json'
}
)
puts response
import requests
subdomain = '<subdomain>'
access_token = 'e00a5f82c0c33170bb455b051e9870f7cb03cf25f3dede395fc1674818c70bd3'
user_id = 35510511
device_id = 525509
uri = 'https://{}.onelogin.com/api/1/users/{}/otp_devices/{}/trigger'.format(subdomain, user_id, device_id)
r = requests.post(uri,
headers = {
'Authorization': 'Bearer {}'.format(access_token),
'Content-Type': 'application/json'
}
)
response = r.json()
print(response)
CloseableHttpClient client = HttpClientBuilder.create().build();
String subdomain = "<subdomain>";
String accessToken = "50266a3de33d45317563b64c1c0bd0ea6c403acd3cff5f57d4cfaf71659a9dc9";
Integer userId = 35510511;
Integer deviceId = 525509;
String uri = String.format("https://%s.onelogin.com/api/1/users/%s/otp_devices/%s/trigger",
subdomain, userId, deviceId);
HttpPost request = new HttpPost(uri);
request.setHeader("Authorization", String.format("Bearer %s", accessToken));
request.addHeader("Content-Type", "application/json");
try {
CloseableHttpResponse reponse = client.execute(request);
String content = EntityUtils.toString(reponse.getEntity());
JSONObject json = new JSONObject(content);
System.out.println(json);
} catch (IOException e) {
e.printStackTrace();
}
Response
{
"status": {
"type": "success",
"code": 200,
"message": "Authentication pending on OL Protect",
"error": false
},
"data": [
{
"user_display_name": "OneLogin Protect",
"active": true,
"device_id": 525509,
"auth_factor_name": "OneLogin Protect",
"type_display_name": "OneLogin Protect",
"id": 36216766,
"state_token": "e89b8d38d12216beef01e354070a91f8938d6198"
}
]
}
3. Verify the token
In the final step, verify that the OTP token has been supplied by the user. However, in this instance, the user has OneLogin protect. Rather than submitting a token, we poll an API endpoint to verify if the user clicked the “Accept” button on their mobile device.
API Reference: Verify a Factor
- Node.js
- Dotnet
- Ruby
- Python
- Java
const request = require('request')
const subdomain = '<subdomain>'
let accessToken = 'b015418a14acbcbb2bdc3bf7f865ecf13ee222f7c8b83d4009b10fc1f30367fd'
let userId = 35510511
let deviceId = 525509
let stateToken = '7eca471e256fffa4d8760dcf893f134279652ab1'
let options = {
method: 'POST',
uri: `https://${subdomain}.onelogin.com/api/1/users/${userId}/otp_devices/${deviceId}/verify`,
auth: {
bearer: accessToken
},
json: {
state_token: stateToken
}
}
request(options, function(error, response, body){
console.log(body)
})
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var subdomain = "<subdomain>";
var userId = 35510511;
var deviceId = 525509;
var uri = String.Format("https://{0}.onelogin.com/api/1/users/{1}/otp_devices/{2}/verify",
subdomain, userId, deviceId);
dynamic body = new {
state_token = "fece0acc04b6c95920edfe539ee7e9979cab9207"
}
var request = new HttpRequestMessage(){
Method = HttpMethod.Post,
RequestUri = new Uri(uri),
Content = new StringContent(JsonConvert.SerializeObject(body))
};
// We add the Content-Type Header like this because otherwise dotnet
// adds the utf-8 charset extension to it which is not compatible with OneLogin
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
dynamic json = JsonConvert.DeserializeObject(responseBody);
require 'httparty'
subdomain = '<subdomain>'
access_token = 'e00a5f82c0c33170bb455b051e9870f7cb03cf25f3dede395fc1674818c70bd3'
user_id = 35510511
device_id = 525509
state_token = '7eca471e256fffa4d8760dcf893f134279652ab1'
response = HTTParty.post("https://#{subdomain}.onelogin.com/api/1/users/#{user_id}/otp_devices/#{device_id}/verify",
body: { state_token: state_token }.to_json,
headers: {
'Authorization' => "Bearer #{access_token}",
'Content-Type' => 'application/json'
}
)
puts response
import requests
subdomain = '<subdomain>'
access_token = 'e00a5f82c0c33170bb455b051e9870f7cb03cf25f3dede395fc1674818c70bd3'
user_id = 35510511
device_id = 525509
state_token = '7eca471e256fffa4d8760dcf893f134279652ab1'
uri = 'https://{}.onelogin.com/api/1/users/{}/otp_devices/{}/verify'.format(subdomain, user_id, device_id)
r = requests.post(uri,
headers = {
'Authorization': 'Bearer {}'.format(access_token),
'Content-Type': 'application/json'
},
json = {
state_token: state_token
}
)
response = r.json()
print(response)
CloseableHttpClient client = HttpClientBuilder.create().build();
String subdomain = "<subdomain>";
String accessToken = "50266a3de33d45317563b64c1c0bd0ea6c403acd3cff5f57d4cfaf71659a9dc9";
Integer userId = 35510511;
Integer deviceId = 525509;
String uri = String.format("https://%s.onelogin.com/api/1/users/%s/otp_devices/%s/verify",
subdomain, userId, deviceId);
String state_token = "1b617a09216d4818a1926720377b6760c038ad76";
HttpPost request = new HttpPost(uri);
request.setHeader("Authorization", String.format("Bearer %s", accessToken));
request.addHeader("Content-Type", "application/json");
request.setEntity(new StringEntity("{ \"state_token\": \"" + state_token + "\" }", "UTF-8"));
try {
CloseableHttpResponse reponse = client.execute(request);
String content = EntityUtils.toString(reponse.getEntity());
JSONObject json = new JSONObject(content);
System.out.println(json);
} catch (IOException e) {
e.printStackTrace();
}
Response
Before the user has acknowledged the push notification
{
"status": {
"type": "Unauthorized",
"code": 401,
"message": "Authentication pending on OL Protect",
"error": true
}
}
Once the user has clicked Accept
{
"status": {
"type": "success",
"code": 200,
"message": "Success",
"error": false
}
}
Have a Question?
Found a problem or a bug? Submit a support ticket.
Looking for walkthroughs or how-to guides on OneLogin's user and admin features? Check out the documentation in our Knowledge Base.
Have a product idea or request? Share it with us in our Ideas Portal.