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

Introduce support for AAD Device Code Flow authentication #597

Merged
merged 17 commits into from Jul 27, 2020
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 30 additions & 0 deletions doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs
@@ -0,0 +1,30 @@
//<Snippet1>
using System;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Data.SqlClient;

namespace CustomAuthenticationProviderExamples
{
public class Program
{
public static void Main()
{
SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(CustomDeviceFlowCallback);
SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider);
using (SqlConnection sqlConnection = new SqlConnection("Server=<myserver>.database.windows.net;Authentication=Active Directory Device Code Flow;Database=<db>;"))
{
sqlConnection.Open();
Console.WriteLine("Connected successfully!");
}
}

private Task CustomDeviceFlowCallback(DeviceCodeResult result)
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
{
// Provide custon logic to process result information and read device code.
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
Console.WriteLine(result.Message);
return Task.FromResult(0);
}
}
}
//</Snippet1>
56 changes: 56 additions & 0 deletions doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs
@@ -0,0 +1,56 @@
//<Snippet1>
using System;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Data.SqlClient;

namespace CustomAuthenticationProviderExamples
{
/// <summary>
/// Example demonstrating creating a custom device code flow authentication provider and attaching it to the driver.
/// This is helpful for applications that wish to override the Callback for the Device Code Result implemented by the SqlClient driver.
/// </summary>
public class CustomDeviceCodeFlowAzureAuthenticationProvider : SqlAuthenticationProvider
{
public override async Task<SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters)
{
string clientId = "my-client-id";
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
string clientName = "My Application Name";
string s_defaultScopeSuffix = "/.default";

string[] scopes = new string[] { parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix };

IPublicClientApplication app = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(parameters.Authority)
.WithClientName(clientName)
.WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
.Build();

AuthenticationResult result = await app.AcquireTokenWithDeviceCode(scopes,
deviceCodeResult => CustomDeviceFlowCallback(deviceCodeResult)).ExecuteAsync();
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn);
}

public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) => authenticationMethod.Equals(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow);

private Task CustomDeviceFlowCallback(DeviceCodeResult result)
{
Console.WriteLine(result.Message);
return Task.FromResult(0);
}
}

public class Program
{
public static void Main()
{
SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, new CustomDeviceCodeFlowAzureAuthenticationProvider());
using (SqlConnection sqlConnection = new SqlConnection("Server=<myserver>.database.windows.net;Authentication=Active Directory Device Code Flow;Database=<db>;"))
{
sqlConnection.Open();
Console.WriteLine("Connected successfully!");
}
}
}
}
//</Snippet1>
@@ -0,0 +1,82 @@
<docs>
<members name="ActiveDirectoryAuthenticationProvider">
<ActiveDirectoryAuthenticationProvider>
<summary>
This class implements <see cref="T:Microsoft.Data.SqlClient.SqlAuthenticationProvider" /> and is used for active directory federated authentication mechanisms.
</summary>
</ActiveDirectoryAuthenticationProvider>
<ctor>
<summary>
Initializes the <see cref="T:Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProvider" /> class.
</summary>
</ctor>
<ctor2>
<param name="deviceCodeFlowCallbackMethod">The callback method to be used by driver when performing 'Active Directory Device Code Flow' authentication.</param>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
<summary>
Initializes the <see cref="T:Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProvider" /> class with provided device code flow callback method.
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
</summary>
</ctor2>
<AcquireTokenAsync>
<param name="parameters">The Active Directory authentication parameters passed by the driver to authentication providers.</param>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
<summary>Acquires a security token from the authority.</summary>
<returns>Represents an asynchronous operation that returns the AD authentication token.</returns>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
</AcquireTokenAsync>
<SetDeviceCodeFlowCallback>
<param name="deviceCodeFlowCallbackMethod">The callback method to be used by driver when performing 'Active Directory Device Code Flow' authentication.</param>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
<summary>Sets callback method that overrides driver's default implementation to process result when performing 'Active Directory Device Code Flow' authentication.</summary>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
</SetDeviceCodeFlowCallback>
<SetParentActivityOrWindowFunc>
<param name="parentActivityOrWindowFunc">The parent as an object, in order to be used from shared NetStandard assemblies.</param>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
<summary>Sets a reference to the ViewController (if using Xamarin.iOS), Activity (if using Xamarin.Android) IWin32Window or IntPtr (if using .Net Framework). Used for invoking the browser for Active Directory Interactive authentication.</summary>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
<remarks>Mandatory only on Android to be set. See https://aka.ms/msal-net-android-activity for further documentation and details.</remarks>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
</SetParentActivityOrWindowFunc>
<SetIWin32WindowFunc>
<param name="iWin32WindowFunc">A function to return the current window.</param>
<summary>Sets a reference to the current <see cref="T:System.Windows.Forms.IWin32Window" /> that triggers the browser to be shown. Used to center the browser that pop-up onto this window."</summary>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
</SetIWin32WindowFunc>
<SetCustomWebUI>
<param name="customWebUi">Customer implementation for the Web UI</param>
<summary>Sets a custom Web UI that will let the user sign-in with Azure AD, present consent if needed, and get back the authorization code. Applicable when working with Active Directory Interactive authentication.</summary>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
</SetCustomWebUI>
<BeforeLoad>
<param name="authentication">The authentication method.</param>
<summary>This method is called immediately before the provider is added to SQL drivers registry. </summary>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
<remarks>Avoid performing long-waiting tasks in this method, since it can block other threads from accessing the provider registry.</remarks>
</BeforeLoad>
<BeforeUnload>
<param name="authentication">The authentication method.</param>
<summary>This method is called immediately before the provider is removed from the SQL drivers registry. </summary>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
<remarks>For example, this method is called when a different provider with the same authentication method overrides this provider in the SQL drivers registry. Avoid performing long-waiting task in this method, since it can block other threads from accessing the provider registry.</remarks>
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
</BeforeUnload>
<IsSupported>
<param name="authentication">The authentication method.</param>
<summary>Indicates whether the specified authentication method is supported.</summary>
<returns>
<see langword="true" /> if the specified authentication method is supported; otherwise, <see langword="false" />.
</returns>
<remarks>
<format type="text/markdown">
<![CDATA[

## Remarks
The supported authentication methods are:

|Authentication Method|
|----------------|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryPassword%2A>|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryIntegrated%2A>|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryInteractive%2A>|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryServicePrincipal%2A>|
|<xref:Microsoft.Data.SqlClient.SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow%2A>|

## Examples
The following example demonstrates providing a custom device flow callback to SqlClient driver for Device Code Flow authentication method:
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved

[!code-csharp[ActiveDirectory_DeviceCodeFlowCallback Example#1](~/../sqlclient/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs#1)]

]]>
</format>
</remarks>
</IsSupported>
</members>
</docs>
Expand Up @@ -29,5 +29,9 @@
<summary>The authentication method uses Active Directory Service Principal. Use Active Directory Service Principal to connect to a SQL Database using the client ID and secret of a service principal identity.</summary>
<value>5</value>
</ActiveDirectoryServicePrincipal>
<ActiveDirectoryDeviceCodeFlow>
<summary>The authentication method uses Active Directory Device Code Flow. Use Active Directory Device Code Flow to connect to a SQL Database from devices and operating systems that do not provide a Web browser, using another device to perform interactive authentication.</summary>
<value>6</value>
</ActiveDirectoryDeviceCodeFlow>
</members>
</docs>
1 change: 1 addition & 0 deletions src/Microsoft.Data.SqlClient.sln
Expand Up @@ -78,6 +78,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.Sql", "Micro
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient", "{C05F4FFE-6A14-4409-AA0A-10630BE4F1EE}"
ProjectSection(SolutionItems) = preProject
..\doc\snippets\Microsoft.Data.SqlClient\ActiveDirectoryAuthenticationProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\ActiveDirectoryAuthenticationProvider.xml
..\doc\snippets\Microsoft.Data.SqlClient\ApplicationIntent.xml = ..\doc\snippets\Microsoft.Data.SqlClient\ApplicationIntent.xml
..\doc\snippets\Microsoft.Data.SqlClient\OnChangeEventHandler.xml = ..\doc\snippets\Microsoft.Data.SqlClient\OnChangeEventHandler.xml
..\doc\snippets\Microsoft.Data.SqlClient\PoolBlockingPeriod.xml = ..\doc\snippets\Microsoft.Data.SqlClient\PoolBlockingPeriod.xml
Expand Down
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// ------------------------------------------------------------------------------
// Changes to this file must follow the http://aka.ms/api-review process.
// ------------------------------------------------------------------------------

namespace Microsoft.Data.SqlClient
{
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/ActiveDirectoryAuthenticationProvider/*'/>
public sealed partial class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider
{
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/SetParentActivityOrWindowFunc/*'/>
public void SetParentActivityOrWindowFunc(System.Func<object> parentActivityOrWindowFunc) { }
}
}
Expand Up @@ -30,6 +30,26 @@ public sealed partial class SqlNotificationRequest
}
namespace Microsoft.Data.SqlClient
{
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/ActiveDirectoryAuthenticationProvider/*'/>
public sealed partial class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider
{
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/ctor/*'/>
public ActiveDirectoryAuthenticationProvider() { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/ctor2/*'/>
public ActiveDirectoryAuthenticationProvider(System.Func<Microsoft.Identity.Client.DeviceCodeResult, System.Threading.Tasks.Task> deviceCodeFlowCallbackMethod) { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/AcquireTokenAsync/*'/>
public override System.Threading.Tasks.Task<SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/SetDeviceCodeFlowCallback/*'/>
public void SetDeviceCodeFlowCallback(System.Func<Microsoft.Identity.Client.DeviceCodeResult, System.Threading.Tasks.Task> deviceCodeFlowCallbackMethod) { }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/SetCustomWebUI/*'/>
public void SetCustomWebUI(Microsoft.Identity.Client.Extensibility.ICustomWebUi customWebUi) { }
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/IsSupported/*'/>
public override bool IsSupported(SqlAuthenticationMethod authentication) { throw null; }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/BeforeLoad/*'/>
public override void BeforeLoad(SqlAuthenticationMethod authentication) { }
/// <include file='../../../..//doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/BeforeUnload/*'/>
public override void BeforeUnload(SqlAuthenticationMethod authentication) { }
}
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/ApplicationIntent.xml' path='docs/members[@name="ApplicationIntent"]/ApplicationIntent/*'/>
public enum ApplicationIntent
{
Expand Down Expand Up @@ -69,6 +89,8 @@ public enum SqlAuthenticationMethod
ActiveDirectoryPassword = 2,
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml' path='docs/members[@name="SqlAuthenticationMethod"]/ActiveDirectoryServicePrincipal/*'/>
ActiveDirectoryServicePrincipal = 5,
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml' path='docs/members[@name="SqlAuthenticationMethod"]/ActiveDirectoryDeviceCodeFlow/*'/>
ActiveDirectoryDeviceCodeFlow = 6,
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml' path='docs/members[@name="SqlAuthenticationMethod"]/NotSpecified/*'/>
NotSpecified = 0,
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml' path='docs/members[@name="SqlAuthenticationMethod"]/SqlPassword/*'/>
Expand Down
Expand Up @@ -19,4 +19,10 @@
<Compile Include="Microsoft.Data.SqlClient.NetCoreApp.cs" />
<PackageReference Include="System.Security.Cryptography.Cng" Version="$(SystemSecurityCryptographyCngVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)' == 'netstandard'">
<Compile Include="Microsoft.Data.SqlClient.NetStandard.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Identity.Client" Version="$(MicrosoftIdentityClientVersion)" />
</ItemGroup>
</Project>
Expand Up @@ -21,6 +21,9 @@
<PropertyGroup Condition="'$(TargetGroup)' == 'netcoreapp'">
<DefineConstants>$(DefineConstants);netcoreapp;</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetGroup)' == 'netstandard'">
<DefineConstants>$(DefineConstants);netstandard;</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetGroup)' == 'netcoreapp' AND '$(TargetFramework)' != 'netcoreapp2.1'">
<DefineConstants>$(DefineConstants);NETCORE3</DefineConstants>
</PropertyGroup>
Expand Down
Expand Up @@ -103,10 +103,11 @@ internal static string ConvertToString(object value)
const string ActiveDirectoryIntegratedString = "Active Directory Integrated";
const string ActiveDirectoryInteractiveString = "Active Directory Interactive";
const string ActiveDirectoryServicePrincipalString = "Active Directory Service Principal";
const string ActiveDirectoryDeviceCodeFlowString = "Active Directory Device Code Flow";

internal static bool TryConvertToAuthenticationType(string value, out SqlAuthenticationMethod result)
{
Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 6, "SqlAuthenticationMethod enum has changed, update needed");
Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 7, "SqlAuthenticationMethod enum has changed, update needed");

bool isSuccess = false;

Expand Down Expand Up @@ -140,6 +141,12 @@ internal static bool TryConvertToAuthenticationType(string value, out SqlAuthent
result = SqlAuthenticationMethod.ActiveDirectoryServicePrincipal;
isSuccess = true;
}
else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryDeviceCodeFlowString)
|| StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, CultureInfo.InvariantCulture)))
{
result = SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow;
isSuccess = true;
}
else
{
result = DbConnectionStringDefaults.Authentication;
Expand Down Expand Up @@ -486,6 +493,7 @@ internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod valu
|| value == SqlAuthenticationMethod.ActiveDirectoryIntegrated
|| value == SqlAuthenticationMethod.ActiveDirectoryInteractive
|| value == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal
|| value == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow
|| value == SqlAuthenticationMethod.NotSpecified;
}

Expand All @@ -505,6 +513,8 @@ internal static string AuthenticationTypeToString(SqlAuthenticationMethod value)
return ActiveDirectoryInteractiveString;
case SqlAuthenticationMethod.ActiveDirectoryServicePrincipal:
return ActiveDirectoryServicePrincipalString;
case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow:
return ActiveDirectoryDeviceCodeFlowString;
default:
return null;
}
Expand Down