Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding authenticator enrollment and verification to authentication client #695

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/Auth0.AuthenticationApi/AuthenticationApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -350,7 +351,33 @@ await AssertIdTokenValidIfExisting(response.IdToken, request.ClientId, request.S
}

/// <inheritdoc/>
public async Task<MfaOobTokenResponse> GetTokenAsync(MfaOobTokenRequest request, CancellationToken cancellationToken = default)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}

var body = new Dictionary<string, string>()
{
{ "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<MfaOobTokenResponse>(
HttpMethod.Post,
tokenUri,
body,
cancellationToken: cancellationToken);
}

/// <inheritdoc/>
public Task RevokeRefreshTokenAsync(RevokeRefreshTokenRequest request, CancellationToken cancellationToken = default)
{
if (request == null)
Expand Down Expand Up @@ -494,6 +521,22 @@ public Task<PushedAuthorizationRequestResponse> PushedAuthorizationRequestAsync(
cancellationToken: cancellationToken
);
}

/// <inheritdoc/>
public Task<AssociateMfaAuthenticatorResponse> AssociateMfaAuthenticatorAsync(AssociateMfaAuthenticatorRequest request, CancellationToken cancellationToken = default)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}

return connection.SendAsync<AssociateMfaAuthenticatorResponse>(
HttpMethod.Post,
BuildUri("mfa/associate"),
request,
BuildHeaders(request.Token),
cancellationToken);
}

/// <summary>
/// Disposes of any owned disposable resources such as a <see cref="IAuthenticationConnection"/>.
Expand Down
23 changes: 23 additions & 0 deletions src/Auth0.AuthenticationApi/IAuthenticationApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ public interface IAuthenticationApiClient : IDisposable
/// </remarks>
Task<AccessTokenResponse> GetTokenAsync(DeviceCodeTokenRequest request, CancellationToken cancellationToken = default);

/// <summary>
/// Requests an Access Token using Oob MFA verification.
/// </summary>
/// <param name="request"><see cref="MfaOobTokenRequest"/> containing request details to verify oob.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns><see cref="Task"/> representing the async operation containing
/// a <see cref="MfaOobTokenResponse" /> with the requested tokens.</returns>
Task<MfaOobTokenResponse> GetTokenAsync(MfaOobTokenRequest request, CancellationToken cancellationToken = default);

/// <summary>
/// Revokes refresh token provided in request.
/// </summary>
Expand Down Expand Up @@ -180,5 +189,19 @@ public interface IAuthenticationApiClient : IDisposable
/// a <see cref="PushedAuthorizationRequestResponse" /> with the details of the response.</returns>
Task<PushedAuthorizationRequestResponse> PushedAuthorizationRequestAsync(PushedAuthorizationRequest request,
CancellationToken cancellationToken = default);

/// <summary>
/// Sends a Mfa enrollment request
/// </summary>
/// <param name="request"><see cref="AssociateMfaAuthenticatorRequest"/>containing information to enroll a new Authenticator.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns><see cref="Task"/> representing the async operation containing
/// a <see cref="AssociateMfaAuthenticatorResponse" /> with the details of the response.</returns>
/// <returns></returns>
Task<AssociateMfaAuthenticatorResponse> AssociateMfaAuthenticatorAsync(
AssociateMfaAuthenticatorRequest request,
CancellationToken cancellationToken = default);


}
}
Original file line number Diff line number Diff line change
@@ -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; }

/// <summary>Your application's Client ID.</summary>
[JsonProperty("client_id")]
public string ClientId { get; set; }

/// <summary>
/// A JWT containing a signed assertion with your application credentials. Required when Private Key JWT is your application authentication
/// method.
/// </summary>
[JsonProperty("client_assertion")]
public string ClientAssertion { get; set; }

/// <summary>
/// Your application's Client Secret. Required when the Token Endpoint Authentication Method field in your Application Settings is Post or
/// Basic.
/// </summary>
[JsonProperty("client_secret")]
public string ClientSecret { get; set; }

/// <summary>
/// The value is urn:ietf:params:oauth:client-assertion-type:jwt-bearer. Required when Private Key JWT is the application authentication
/// method.
/// </summary>
[JsonProperty("client_assertion_type")]
public string ClientAssertionType { get; set; }

/// <summary>The type of authenticators supported by the client. Value is an array with values "otp" or "oob".</summary>
[JsonProperty("authenticator_types")]
public List<string> AuthenticatorTypes { get; set; }

/// <summary>
/// The type of OOB channels supported by the client. An array with values "auth0", "sms", "voice". Required if authenticator_types include
/// oob.
/// </summary>
[JsonProperty("oob_channels")]
public List<string> OobChannels { get; set; }

/// <summary>The phone number to use for SMS or Voice. Required if oob_channels includes sms or voice.</summary>
[JsonProperty("phone_number")]
public string PhoneNumber { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -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<string> RecoveryCodes { get; set; }
}
}
44 changes: 44 additions & 0 deletions src/Auth0.AuthenticationApi/Models/MfaOobTokenRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Microsoft.IdentityModel.Tokens;

namespace Auth0.AuthenticationApi.Models
{
public class MfaOobTokenRequest : IClientAuthentication
{
/// <summary>
/// Your application's Client ID.
/// </summary>
public string ClientId { get; set; }

/// <summary>
/// Your application's Client Secret.
/// Required when the Token Endpoint Authentication Method field at your Application Settings is Post or Basic.
/// </summary>
public string ClientSecret { get; set; }
czf marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Security Key to use with Client Assertion
/// </summary>
public SecurityKey ClientAssertionSecurityKey { get; set; }

/// <summary>
/// Algorithm for the Security Key to use with Client Assertion
/// </summary>
public string ClientAssertionSecurityKeyAlgorithm { get; set; }

/// <summary>
/// The mfa_token you received from mfa_required error or access token with enroll scope and audience: https://{yourDomain}/mfa/
/// </summary>
public string MfaToken { get; set; }

/// <summary>
/// The oob code received from the challenge request.
/// </summary>
public string OobCode { get; set; }

/// <summary>
/// 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.
/// </summary>
public string BindingCode { get; set; }
}
}
19 changes: 19 additions & 0 deletions src/Auth0.AuthenticationApi/Models/MfaOobTokenResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Newtonsoft.Json;

namespace Auth0.AuthenticationApi.Models
{
public class MfaOobTokenResponse : TokenBase
{
/// <summary>
/// The value of the different scopes issued in the token
/// </summary>
[JsonProperty("scope")]
public string Scope { get; set; }

/// <summary>
/// The lifetime (in seconds) of the token
/// </summary>
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
}
}
60 changes: 60 additions & 0 deletions tests/Auth0.AuthenticationApi.IntegrationTests/MfaTests.cs
Original file line number Diff line number Diff line change
@@ -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<string>() { "oob" },
OobChannels = new List<string>() { "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");
}
}
}
Loading