diff --git a/src/Auth0.AuthenticationApi/AuthenticationApiClient.cs b/src/Auth0.AuthenticationApi/AuthenticationApiClient.cs
index 2d3c3297..af8c42e7 100644
--- a/src/Auth0.AuthenticationApi/AuthenticationApiClient.cs
+++ b/src/Auth0.AuthenticationApi/AuthenticationApiClient.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
+using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -350,7 +351,33 @@ await AssertIdTokenValidIfExisting(response.IdToken, request.ClientId, request.S
}
///
+ public async Task GetTokenAsync(MfaOobTokenRequest request, CancellationToken cancellationToken = default)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+
+ var body = new Dictionary()
+ {
+ { "grant_type", "http://auth0.com/oauth/grant-type/mfa-oob" },
+ { "client_id", request.ClientId },
+ { "mfa_token", request.MfaToken},
+ { "oob_code", request.OobCode},
+ };
+
+ body.AddIfNotEmpty("binding_code", request.BindingCode);
+
+ ApplyClientAuthentication(request, body);
+
+ return await connection.SendAsync(
+ HttpMethod.Post,
+ tokenUri,
+ body,
+ cancellationToken: cancellationToken);
+ }
+ ///
public Task RevokeRefreshTokenAsync(RevokeRefreshTokenRequest request, CancellationToken cancellationToken = default)
{
if (request == null)
@@ -494,6 +521,22 @@ public Task PushedAuthorizationRequestAsync(
cancellationToken: cancellationToken
);
}
+
+ ///
+ public Task AssociateMfaAuthenticatorAsync(AssociateMfaAuthenticatorRequest request, CancellationToken cancellationToken = default)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+
+ return connection.SendAsync(
+ HttpMethod.Post,
+ BuildUri("mfa/associate"),
+ request,
+ BuildHeaders(request.Token),
+ cancellationToken);
+ }
///
/// Disposes of any owned disposable resources such as a .
diff --git a/src/Auth0.AuthenticationApi/IAuthenticationApiClient.cs b/src/Auth0.AuthenticationApi/IAuthenticationApiClient.cs
index 268ad63e..6724c309 100644
--- a/src/Auth0.AuthenticationApi/IAuthenticationApiClient.cs
+++ b/src/Auth0.AuthenticationApi/IAuthenticationApiClient.cs
@@ -125,6 +125,15 @@ public interface IAuthenticationApiClient : IDisposable
///
Task GetTokenAsync(DeviceCodeTokenRequest request, CancellationToken cancellationToken = default);
+ ///
+ /// Requests an Access Token using Oob MFA verification.
+ ///
+ /// containing request details to verify oob.
+ /// The cancellation token to cancel operation.
+ /// representing the async operation containing
+ /// a with the requested tokens.
+ Task GetTokenAsync(MfaOobTokenRequest request, CancellationToken cancellationToken = default);
+
///
/// Revokes refresh token provided in request.
///
@@ -180,5 +189,19 @@ public interface IAuthenticationApiClient : IDisposable
/// a with the details of the response.
Task PushedAuthorizationRequestAsync(PushedAuthorizationRequest request,
CancellationToken cancellationToken = default);
+
+ ///
+ /// Sends a Mfa enrollment request
+ ///
+ /// containing information to enroll a new Authenticator.
+ /// The cancellation token to cancel operation.
+ /// representing the async operation containing
+ /// a with the details of the response.
+ ///
+ Task AssociateMfaAuthenticatorAsync(
+ AssociateMfaAuthenticatorRequest request,
+ CancellationToken cancellationToken = default);
+
+
}
}
diff --git a/src/Auth0.AuthenticationApi/Models/AssociateMfaAuthenticatorRequest.cs b/src/Auth0.AuthenticationApi/Models/AssociateMfaAuthenticatorRequest.cs
new file mode 100644
index 00000000..da2a96a0
--- /dev/null
+++ b/src/Auth0.AuthenticationApi/Models/AssociateMfaAuthenticatorRequest.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace Auth0.AuthenticationApi.Models
+{
+ public class AssociateMfaAuthenticatorRequest
+ {
+ [JsonIgnore]
+ public string Token { get; set; }
+
+ /// Your application's Client ID.
+ [JsonProperty("client_id")]
+ public string ClientId { get; set; }
+
+ ///
+ /// A JWT containing a signed assertion with your application credentials. Required when Private Key JWT is your application authentication
+ /// method.
+ ///
+ [JsonProperty("client_assertion")]
+ public string ClientAssertion { get; set; }
+
+ ///
+ /// Your application's Client Secret. Required when the Token Endpoint Authentication Method field in your Application Settings is Post or
+ /// Basic.
+ ///
+ [JsonProperty("client_secret")]
+ public string ClientSecret { get; set; }
+
+ ///
+ /// The value is urn:ietf:params:oauth:client-assertion-type:jwt-bearer. Required when Private Key JWT is the application authentication
+ /// method.
+ ///
+ [JsonProperty("client_assertion_type")]
+ public string ClientAssertionType { get; set; }
+
+ /// The type of authenticators supported by the client. Value is an array with values "otp" or "oob".
+ [JsonProperty("authenticator_types")]
+ public List AuthenticatorTypes { get; set; }
+
+ ///
+ /// The type of OOB channels supported by the client. An array with values "auth0", "sms", "voice". Required if authenticator_types include
+ /// oob.
+ ///
+ [JsonProperty("oob_channels")]
+ public List OobChannels { get; set; }
+
+ /// The phone number to use for SMS or Voice. Required if oob_channels includes sms or voice.
+ [JsonProperty("phone_number")]
+ public string PhoneNumber { get; set; }
+ }
+}
diff --git a/src/Auth0.AuthenticationApi/Models/AssociateMfaAuthenticatorResponse.cs b/src/Auth0.AuthenticationApi/Models/AssociateMfaAuthenticatorResponse.cs
new file mode 100644
index 00000000..9939261c
--- /dev/null
+++ b/src/Auth0.AuthenticationApi/Models/AssociateMfaAuthenticatorResponse.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Auth0.AuthenticationApi.Models
+{
+ public class AssociateMfaAuthenticatorResponse
+ {
+ [JsonProperty("oob_code")]
+ public string OobCode { get; set; }
+
+ [JsonProperty("binding_method")]
+ public string BindingMethod { get; set; }
+
+ [JsonProperty("secret")]
+ public string Secret { get; set; }
+
+ [JsonProperty("barcode_uri")]
+ public string BarcodeUri { get; set; }
+
+ [JsonProperty("authenticator_type")]
+ public string AuthenticatorType { get; set; }
+
+ [JsonProperty("oob_channel")]
+ public string OobChannel { get; set; }
+
+ [JsonProperty("recovery_codes")]
+ public List RecoveryCodes { get; set; }
+ }
+}
diff --git a/src/Auth0.AuthenticationApi/Models/MfaOobTokenRequest.cs b/src/Auth0.AuthenticationApi/Models/MfaOobTokenRequest.cs
new file mode 100644
index 00000000..e47aae40
--- /dev/null
+++ b/src/Auth0.AuthenticationApi/Models/MfaOobTokenRequest.cs
@@ -0,0 +1,44 @@
+using Microsoft.IdentityModel.Tokens;
+
+namespace Auth0.AuthenticationApi.Models
+{
+ public class MfaOobTokenRequest : IClientAuthentication
+ {
+ ///
+ /// Your application's Client ID.
+ ///
+ public string ClientId { get; set; }
+
+ ///
+ /// Your application's Client Secret.
+ /// Required when the Token Endpoint Authentication Method field at your Application Settings is Post or Basic.
+ ///
+ public string ClientSecret { get; set; }
+
+ ///
+ /// Security Key to use with Client Assertion
+ ///
+ public SecurityKey ClientAssertionSecurityKey { get; set; }
+
+ ///
+ /// Algorithm for the Security Key to use with Client Assertion
+ ///
+ public string ClientAssertionSecurityKeyAlgorithm { get; set; }
+
+ ///
+ /// The mfa_token you received from mfa_required error or access token with enroll scope and audience: https://{yourDomain}/mfa/
+ ///
+ public string MfaToken { get; set; }
+
+ ///
+ /// The oob code received from the challenge request.
+ ///
+ public string OobCode { get; set; }
+
+ ///
+ /// A code used to bind the side channel (used to deliver the challenge) with the main channel you are using to authenticate.
+ /// This is usually an OTP-like code delivered as part of the challenge message.
+ ///
+ public string BindingCode { get; set; }
+ }
+}
diff --git a/src/Auth0.AuthenticationApi/Models/MfaOobTokenResponse.cs b/src/Auth0.AuthenticationApi/Models/MfaOobTokenResponse.cs
new file mode 100644
index 00000000..c0dc9363
--- /dev/null
+++ b/src/Auth0.AuthenticationApi/Models/MfaOobTokenResponse.cs
@@ -0,0 +1,19 @@
+using Newtonsoft.Json;
+
+namespace Auth0.AuthenticationApi.Models
+{
+ public class MfaOobTokenResponse : TokenBase
+ {
+ ///
+ /// The value of the different scopes issued in the token
+ ///
+ [JsonProperty("scope")]
+ public string Scope { get; set; }
+
+ ///
+ /// The lifetime (in seconds) of the token
+ ///
+ [JsonProperty("expires_in")]
+ public int ExpiresIn { get; set; }
+ }
+}
diff --git a/tests/Auth0.AuthenticationApi.IntegrationTests/MfaTests.cs b/tests/Auth0.AuthenticationApi.IntegrationTests/MfaTests.cs
new file mode 100644
index 00000000..7c681a19
--- /dev/null
+++ b/tests/Auth0.AuthenticationApi.IntegrationTests/MfaTests.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Auth0.AuthenticationApi.Models;
+using Auth0.Tests.Shared;
+using FluentAssertions;
+using Xunit;
+
+namespace Auth0.AuthenticationApi.IntegrationTests
+{
+ public class MfaTests : TestBase
+ {
+ private readonly AuthenticationApiClient _authenticationApiClient;
+
+ public MfaTests()
+ {
+ _authenticationApiClient = new AuthenticationApiClient(GetVariable("AUTH0_AUTHENTICATION_API_URL"));
+ }
+
+ [Fact(Skip = "Run manually")]
+ public async Task Should_Receive_Associate_Response_For_Sms_Mfa_Enrollment()
+ {
+ var request =
+ new AssociateMfaAuthenticatorRequest()
+ {
+ Token = TestBaseUtils.GetVariable("AUTH0_AUTHENTICATOR_ENROLL_TOKEN"),
+ ClientId = TestBaseUtils.GetVariable("AUTH0_CLIENT_ID"),
+ ClientSecret = TestBaseUtils.GetVariable("AUTH0_CLIENT_SECRET"),
+ AuthenticatorTypes = new List() { "oob" },
+ OobChannels = new List() { "sms" },
+ PhoneNumber = TestBaseUtils.GetVariable("MFA_PHONE_NUMBER")
+ };
+ var response = await _authenticationApiClient.AssociateMfaAuthenticatorAsync(request);
+ response.Should().NotBeNull();
+ response.AuthenticatorType.Should().Be("oob");
+ response.BindingMethod.Should().Be("prompt");
+ response.OobChannel.Should().Be("sms");
+
+ response.OobCode.Should().NotBeNullOrEmpty().And.StartWith("Fe26.");
+ }
+
+ [Fact(Skip = "Run manually")]
+ public async Task Should_Receive_MfaOobTokenResponse_For_Oob_Mfa_Verification()
+ {
+ var request = new MfaOobTokenRequest()
+ {
+ ClientId = TestBaseUtils.GetVariable("AUTH0_CLIENT_ID"),
+ ClientSecret = TestBaseUtils.GetVariable("AUTH0_CLIENT_SECRET"),
+ MfaToken = TestBaseUtils.GetVariable("MFA_TOKEN"),
+ OobCode = TestBaseUtils.GetVariable("MFA_OOB_CODE"),
+ BindingCode = TestBaseUtils.GetVariable("MFA_BINDING_CODE")
+ };
+
+ var response = await _authenticationApiClient.GetTokenAsync(request);
+ response.Should().NotBeNull();
+ response.AccessToken.Should().StartWith("ey");
+ response.ExpiresIn.Should().BeGreaterThan(0);
+ response.TokenType.Should().Be("Bearer");
+ }
+ }
+}