Skip to content

Commit

Permalink
[Identity] User authentication API for applications (Azure#11285)
Browse files Browse the repository at this point in the history
* updates for interactive browser authentication

* adding authenticate methods to DeviceCodeCredential

* adding AuthenticationProfile to options classes

* updating api definition

* incorperate arch board feedback

* updating identity to use msal extensions

* adding token cache storage values for Mac and Linux

* missed AuthenticationRecord rename

* adding tests

* adding test recordings

* address pr feedback

* update api listing

* updating changelog

* updating changelog

* address pr feedback
  • Loading branch information
schaabs authored May 2, 2020
1 parent 350cc8d commit 7ea64c2
Show file tree
Hide file tree
Showing 42 changed files with 1,691 additions and 662 deletions.
3 changes: 2 additions & 1 deletion eng/Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
<PackageReference Update="Microsoft.CodeAnalysis" Version="2.3.0" />
<PackageReference Update="Microsoft.DotNet.ApiCompat" Version="5.0.0-beta.19552.1" />
<PackageReference Update="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="4.5.1" />
<PackageReference Update="Microsoft.Identity.Client" Version="4.1.0" />
<PackageReference Update="Microsoft.Identity.Client" Version="4.10.0" />
<PackageReference Update="Microsoft.Identity.Client.Extensions.Msal" Version="2.8.0-preview" />
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.1.0" />
<PackageReference Update="Microsoft.NETCore.Platforms" Version="2.2.1" />
<PackageReference Update="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" />
Expand Down
9 changes: 9 additions & 0 deletions sdk/identity/Azure.Identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@

## 1.2.0-preview.3 (Unreleased)

### New Features
- First preview of new API for authenticating users with `DeviceCodeCredential` and `InteractiveBrowserCredential`
- Added method `Authenticate` which pro-actively interacts with the user to authenticate if necessary and returns a serializable `AuthenticationRecord`
- Added Options classes `DeviceCodeCredentialOptions` and `InteractiveBrowserCredentialOptions` which support the following new options
- `AuthenticationRecord` enables initializing a credential with an `AuthenticationRecord` returned from a prior call to `Authenticate`
- `DisableAutomaticAuthentication` disables automatic user interaction causing the credential to throw an `AuthenticationRequiredException` when interactive authentication is necessary.
- `EnablePersistentCache` configures these credentials to use a persistent cache shared between credentials which set this option. By default the cache is per credential and in memory only.

## 1.2.0-preview.2

### New Features
- Updates `DefaultAzureCredential` to enable authenticating through Visual Studio
- Updates `DefaultAzureCredential` to enable authentication through Visual Studio Code

Expand Down
46 changes: 46 additions & 0 deletions sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ public partial class AuthenticationFailedException : System.Exception
public AuthenticationFailedException(string message) { }
public AuthenticationFailedException(string message, System.Exception innerException) { }
}
public partial class AuthenticationRecord
{
internal AuthenticationRecord() { }
public string Authority { get { throw null; } }
public string HomeAccountId { get { throw null; } }
public string TenantId { get { throw null; } }
public string Username { get { throw null; } }
public static Azure.Identity.AuthenticationRecord Deserialize(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<Azure.Identity.AuthenticationRecord> DeserializeAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public void Serialize(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { }
public System.Threading.Tasks.Task SerializeAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public partial class AuthenticationRequiredException : Azure.Identity.CredentialUnavailableException
{
public AuthenticationRequiredException(string message, Azure.Core.TokenRequestContext context) : base (default(string)) { }
public AuthenticationRequiredException(string message, Azure.Core.TokenRequestContext context, System.Exception innerException) : base (default(string)) { }
public Azure.Core.TokenRequestContext TokenRequestContext { get { throw null; } }
}
public partial class AuthorizationCodeCredential : Azure.Core.TokenCredential
{
protected AuthorizationCodeCredential() { }
Expand Down Expand Up @@ -69,11 +87,25 @@ public DefaultAzureCredentialOptions() { }
public partial class DeviceCodeCredential : Azure.Core.TokenCredential
{
protected DeviceCodeCredential() { }
public DeviceCodeCredential(System.Func<Azure.Identity.DeviceCodeInfo, System.Threading.CancellationToken, System.Threading.Tasks.Task> deviceCodeCallback, Azure.Identity.DeviceCodeCredentialOptions options = null) { }
public DeviceCodeCredential(System.Func<Azure.Identity.DeviceCodeInfo, System.Threading.CancellationToken, System.Threading.Tasks.Task> deviceCodeCallback, string clientId, Azure.Identity.TokenCredentialOptions options = null) { }
public DeviceCodeCredential(System.Func<Azure.Identity.DeviceCodeInfo, System.Threading.CancellationToken, System.Threading.Tasks.Task> deviceCodeCallback, string tenantId, string clientId, Azure.Identity.TokenCredentialOptions options = null) { }
public virtual Azure.Identity.AuthenticationRecord Authenticate(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Identity.AuthenticationRecord Authenticate(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Identity.AuthenticationRecord> AuthenticateAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Identity.AuthenticationRecord> AuthenticateAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override Azure.Core.AccessToken GetToken(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override System.Threading.Tasks.ValueTask<Azure.Core.AccessToken> GetTokenAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public partial class DeviceCodeCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public DeviceCodeCredentialOptions() { }
public Azure.Identity.AuthenticationRecord AuthenticationRecord { get { throw null; } set { } }
public string ClientId { get { throw null; } set { } }
public bool DisableAutomaticAuthentication { get { throw null; } set { } }
public bool EnablePersistentCache { get { throw null; } set { } }
public string TenantId { get { throw null; } set { } }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public partial struct DeviceCodeInfo
{
Expand All @@ -97,11 +129,25 @@ public EnvironmentCredential(Azure.Identity.TokenCredentialOptions options) { }
public partial class InteractiveBrowserCredential : Azure.Core.TokenCredential
{
public InteractiveBrowserCredential() { }
public InteractiveBrowserCredential(Azure.Identity.InteractiveBrowserCredentialOptions options) { }
public InteractiveBrowserCredential(string clientId) { }
public InteractiveBrowserCredential(string tenantId, string clientId, Azure.Identity.TokenCredentialOptions options = null) { }
public virtual Azure.Identity.AuthenticationRecord Authenticate(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Identity.AuthenticationRecord Authenticate(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Identity.AuthenticationRecord> AuthenticateAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Identity.AuthenticationRecord> AuthenticateAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override Azure.Core.AccessToken GetToken(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override System.Threading.Tasks.ValueTask<Azure.Core.AccessToken> GetTokenAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public partial class InteractiveBrowserCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public InteractiveBrowserCredentialOptions() { }
public Azure.Identity.AuthenticationRecord AuthenticationRecord { get { throw null; } set { } }
public string ClientId { get { throw null; } set { } }
public bool DisableAutomaticAuthentication { get { throw null; } set { } }
public bool EnablePersistentCache { get { throw null; } set { } }
public string TenantId { get { throw null; } set { } }
}
public static partial class KnownAuthorityHosts
{
public static readonly System.Uri AzureChinaCloud;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ internal static class AbstractAcquireTokenParameterBuilderExtensions
public static async Task<AuthenticationResult> ExecuteAsync<T>(this AbstractAcquireTokenParameterBuilder<T> builder, bool async, CancellationToken cancellationToken)
where T : AbstractAcquireTokenParameterBuilder<T>
{
return async
Microsoft.Identity.Client.AuthenticationResult result = async
? await builder.ExecuteAsync(cancellationToken).ConfigureAwait(false)
#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead.
: builder.ExecuteAsync(cancellationToken).GetAwaiter().GetResult();
#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead.

return result;
}
}
}
25 changes: 25 additions & 0 deletions sdk/identity/Azure.Identity/src/AuthenticationAccount.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Identity.Client;

namespace Azure.Identity
{ internal class AuthenticationAccount : IAccount
{
private AuthenticationRecord _profile;

internal AuthenticationAccount(AuthenticationRecord profile)
{
_profile = profile;
}

string IAccount.Username => _profile.Username;

string IAccount.Environment => _profile.Authority;

AccountId IAccount.HomeAccountId => _profile.AccountId;

public static explicit operator AuthenticationAccount(AuthenticationRecord profile) => new AuthenticationAccount(profile);
public static explicit operator AuthenticationRecord(AuthenticationAccount account) => account._profile;
}
}
179 changes: 179 additions & 0 deletions sdk/identity/Azure.Identity/src/AuthenticationRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core.Pipeline;
using Microsoft.Identity.Client;

namespace Azure.Identity
{
/// <summary>
/// Account information relating to an authentication request.
/// </summary>
public class AuthenticationRecord
{
private const string UsernamePropertyName = "username";
private const string AuthorityPropertyName = "authority";
private const string HomeAccountIdPropertyName = "homeAccountId";
private const string TenantIdPropertyName = "tenantId";

private static readonly JsonEncodedText s_usernamePropertyNameBytes = JsonEncodedText.Encode(UsernamePropertyName);
private static readonly JsonEncodedText s_authorityPropertyNameBytes = JsonEncodedText.Encode(AuthorityPropertyName);
private static readonly JsonEncodedText s_homeAccountIdPropertyNameBytes = JsonEncodedText.Encode(HomeAccountIdPropertyName);
private static readonly JsonEncodedText s_tenantIdPropertyNameBytes = JsonEncodedText.Encode(TenantIdPropertyName);

internal AuthenticationRecord()
{

}

internal AuthenticationRecord(AuthenticationResult authResult)
{
Username = authResult.Account.Username;
Authority = authResult.Account.Environment;
AccountId = authResult.Account.HomeAccountId;
TenantId = authResult.TenantId;
}

internal AuthenticationRecord(string username, string authority, string homeAccountId, string tenantId)
{

Username = username;
Authority = authority;
AccountId = new AccountId(homeAccountId);
TenantId = tenantId;
}

/// <summary>
/// The user principal or service principal name of the account.
/// </summary>
public string Username { get; private set; }

/// <summary>
/// The authority host used to authenticate the account.
/// </summary>
public string Authority { get; private set; }

/// <summary>
/// A unique identifier of the account.
/// </summary>
public string HomeAccountId { get => AccountId.Identifier; }

/// <summary>
/// The tenant the account should authenticate in.
/// </summary>
public string TenantId { get; private set; }

internal AccountId AccountId { get; private set; }

/// <summary>
/// Serializes the <see cref="AuthenticationRecord"/> to the specified <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> which the serialized <see cref="AuthenticationRecord"/> will be written to.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public void Serialize(Stream stream, CancellationToken cancellationToken = default)
{
if (stream is null) throw new ArgumentNullException(nameof(stream));

SerializeAsync(stream, false, cancellationToken).EnsureCompleted();
}

/// <summary>
/// Serializes the <see cref="AuthenticationRecord"/> to the specified <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to which the serialized <see cref="AuthenticationRecord"/> will be written.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public async Task SerializeAsync(Stream stream, CancellationToken cancellationToken = default)
{
if (stream is null) throw new ArgumentNullException(nameof(stream));

await SerializeAsync(stream, true, cancellationToken).ConfigureAwait(false);
}


/// <summary>
/// Deserializes the <see cref="AuthenticationRecord"/> from the specified <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> from which the serialized <see cref="AuthenticationRecord"/> will be read.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public static AuthenticationRecord Deserialize(Stream stream, CancellationToken cancellationToken = default)
{
if (stream is null) throw new ArgumentNullException(nameof(stream));

return DeserializeAsync(stream, false, cancellationToken).EnsureCompleted();
}

/// <summary>
/// Deserializes the <see cref="AuthenticationRecord"/> from the specified <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> from which the serialized <see cref="AuthenticationRecord"/> will be read.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public static async Task<AuthenticationRecord> DeserializeAsync(Stream stream, CancellationToken cancellationToken = default)
{
if (stream is null) throw new ArgumentNullException(nameof(stream));

return await DeserializeAsync(stream, true, cancellationToken).ConfigureAwait(false);
}

private async Task SerializeAsync(Stream stream, bool async, CancellationToken cancellationToken)
{
using (var json = new Utf8JsonWriter(stream))
{

json.WriteStartObject();

json.WriteString(s_usernamePropertyNameBytes, Username);

json.WriteString(s_authorityPropertyNameBytes, Authority);

json.WriteString(s_homeAccountIdPropertyNameBytes, HomeAccountId);

json.WriteString(s_tenantIdPropertyNameBytes, TenantId);

json.WriteEndObject();

if (async)
{
await json.FlushAsync(cancellationToken).ConfigureAwait(false);
}
else
{
json.Flush();
}
}
}

private static async Task<AuthenticationRecord> DeserializeAsync(Stream stream, bool async, CancellationToken cancellationToken)
{
var authProfile = new AuthenticationRecord();

using JsonDocument doc = async ? await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken).ConfigureAwait(false) : JsonDocument.Parse(stream);

foreach (JsonProperty prop in doc.RootElement.EnumerateObject())
{
switch (prop.Name)
{
case UsernamePropertyName:
authProfile.Username = prop.Value.GetString();
break;
case AuthorityPropertyName:
authProfile.Authority = prop.Value.GetString();
break;
case HomeAccountIdPropertyName:
authProfile.AccountId = new AccountId(prop.Value.GetString());
break;
case TenantIdPropertyName:
authProfile.TenantId = prop.Value.GetString();
break;
}
}

return authProfile;
}
}
}
Loading

0 comments on commit 7ea64c2

Please sign in to comment.