Skip to content

Commit

Permalink
Introduce support for AAD Device Code Flow authentication (#597)
Browse files Browse the repository at this point in the history
  • Loading branch information
cheenamalhotra authored Jul 27, 2020
1 parent 89a85f2 commit b5319c4
Show file tree
Hide file tree
Showing 40 changed files with 799 additions and 130 deletions.
30 changes: 30 additions & 0 deletions doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs
Original file line number Diff line number Diff line change
@@ -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 static Task CustomDeviceFlowCallback(DeviceCodeResult result)
{
// Provide custom logic to process result information and read device code.
Console.WriteLine(result.Message);
return Task.FromResult(0);
}
}
}
//</Snippet1>
56 changes: 56 additions & 0 deletions doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs
Original file line number Diff line number Diff line change
@@ -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";
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();
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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<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 when performing 'Active Directory Device Code Flow' authentication.</param>
<summary>
Initializes the <see cref="T:Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProvider" /> class with the provided device code flow callback method.
</summary>
</ctor2>
<AcquireTokenAsync>
<param name="parameters">The Active Directory authentication parameters passed to authentication providers.</param>
<summary>Acquires a security token from the authority.</summary>
<returns>Represents an asynchronous operation that returns the authentication token.</returns>
</AcquireTokenAsync>
<SetDeviceCodeFlowCallback>
<param name="deviceCodeFlowCallbackMethod">The callback method to be used when performing 'Active Directory Device Code Flow' authentication.</param>
<summary>Sets the callback method, overriding the default implementation that processes the result when performing 'Active Directory Device Code Flow' authentication.</summary>
</SetDeviceCodeFlowCallback>
<SetParentActivityOrWindowFunc>
<param name="parentActivityOrWindowFunc">The parent as an object, in order to be used from shared .NET Standard assemblies.</param>
<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>
<remarks>Mandatory to be set only on Android. See https://aka.ms/msal-net-android-activity for further documentation and details.</remarks>
</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 pop-up onto this window."</summary>
</SetIWin32WindowFunc>
<SetAcquireAuthorizationCodeAsyncCallback>
<param name="acquireAuthorizationCodeAsyncCallback">The callback method to be called by MSAL.NET to delegate the Web user interface with the Secure Token Service (STS).</param>
<summary>Sets a callback method which is invoked with a custom Web UI instance that will let the user sign-in with Azure Active Directory, present consent if needed, and get back the authorization code. Applicable when working with Active Directory Interactive authentication.</summary>
<remarks>The "authorizationUri" is crafted to leverage PKCE in order to protect the token from a man in the middle attack. Only MSAL.NET can redeem the code. In the event of cancellation, the implementer should return <see cref="T:System.OperationCanceledException" />.</remarks>
</SetAcquireAuthorizationCodeAsyncCallback>
<BeforeLoad>
<param name="authentication">The authentication method.</param>
<summary>This method is called immediately before the provider is added to authentication provider registry. </summary>
<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 authentication provider registry. </summary>
<remarks>For example, this method is called when a different provider with the same authentication method overrides this provider in the authentication provider registry. Avoid performing long-waiting task in this method, since it can block other threads from accessing the provider registry.</remarks>
</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 for the Device Code Flow authentication method:
[!code-csharp[ActiveDirectory_DeviceCodeFlowCallback Example#1](~/../sqlclient/doc/samples/AADAuthenticationCustomDeviceFlowCallback.cs#1)]
]]>
</format>
</remarks>
</IsSupported>
</members>
</docs>
Original file line number Diff line number Diff line change
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>
113 changes: 66 additions & 47 deletions doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml
Original file line number Diff line number Diff line change
@@ -1,49 +1,68 @@
<docs>
<members name="SqlAuthenticationProvider">
<SqlAuthenticationProvider>
<summary>Defines the core behavior of authentication providers and provides a base class for derived classes. </summary>
<remarks>Derived classes must provide a parameterless constructor if they can be instantiated from the app.config file.</remarks>
</SqlAuthenticationProvider>
<ctor>
<summary>Called from constructors in derived classes to initialize the <see cref="T:Microsoft.Data.SqlClient.SqlAuthenticationProvider" /> class.</summary>
</ctor>
<GetProvider>
<param name="authenticationMethod">The authentication method.</param>
<summary>Gets an authentication provider by method.</summary>
<returns>The authentication provider or <see langword="null" /> if not found.</returns>
<remarks>To be added.</remarks>
</GetProvider>
<SetProvider>
<param name="authenticationMethod">The authentication method.</param>
<param name="provider">The authentication provider.</param>
<summary>Sets an authentication provider by method.</summary>
<returns>
<see langword="true" /> if the operation succeeded; otherwise, <see langword="false" /> (for example, the existing provider disallows overriding).
</returns>
<remarks>To be added.</remarks>
</SetProvider>
<BeforeLoad>
<param name="authenticationMethod">The authentication method.</param>
<summary>This method is called immediately before the provider is added to SQL drivers registry. </summary>
<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="authenticationMethod">The authentication method.</param>
<summary>This method is called immediately before the provider is removed from the SQL drivers registry. </summary>
<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>
</BeforeUnload>
<IsSupported>
<param name="authenticationMethod">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>To be added.</remarks>
</IsSupported>
<AcquireTokenAsync>
<param name="parameters">The Active Directory authentication parameters passed by the driver to authentication providers.</param>
<summary>Acquires a security token from the authority.</summary>
<returns>Represents an asynchronous operation that returns the AD authentication token.</returns>
<remarks>To be added.</remarks>
</AcquireTokenAsync>
</members>
<members name="SqlAuthenticationProvider">
<SqlAuthenticationProvider>
<summary>Defines the core behavior of authentication providers and provides a base class for derived classes. </summary>
<remarks>
<format type="text/markdown">
<![CDATA[
## Remarks
Derived classes must provide a parameterless constructor if they can be instantiated from the app.config file.
## Examples
The following example demonstrates implementing a custom SqlAuthenticationProvider and providing the same to SqlClient for overriding Device Code Flow authentication mode:
[!code-csharp[ActiveDirectory_DeviceCodeFlowCallback Example#1](~/../sqlclient/doc/samples/CustomDeviceCodeFlowAzureAuthenticationProvider.cs#1)]
]]>
</format>
</remarks>
</SqlAuthenticationProvider>
<ctor>
<summary>
Called from constructors in derived classes to initialize the <see cref="T:Microsoft.Data.SqlClient.SqlAuthenticationProvider" /> class.
</summary>
</ctor>
<GetProvider>
<param name="authenticationMethod">The authentication method.</param>
<summary>Gets an authentication provider by method.</summary>
<returns>
The authentication provider or <see langword="null" /> if not found.
</returns>
<remarks>To be added.</remarks>
</GetProvider>
<SetProvider>
<param name="authenticationMethod">The authentication method.</param>
<param name="provider">The authentication provider.</param>
<summary>Sets an authentication provider by method.</summary>
<returns>
<see langword="true" /> if the operation succeeded; otherwise, <see langword="false" /> (for example, the existing provider disallows overriding).
</returns>
<remarks>To be added.</remarks>
</SetProvider>
<BeforeLoad>
<param name="authenticationMethod">The authentication method.</param>
<summary>This method is called immediately before the provider is added to SQL drivers registry. </summary>
<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="authenticationMethod">The authentication method.</param>
<summary>This method is called immediately before the provider is removed from the SQL drivers registry. </summary>
<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>
</BeforeUnload>
<IsSupported>
<param name="authenticationMethod">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>To be added.</remarks>
</IsSupported>
<AcquireTokenAsync>
<param name="parameters">The Active Directory authentication parameters passed by the driver to authentication providers.</param>
<summary>Acquires a security token from the authority.</summary>
<returns>Represents an asynchronous operation that returns the AD authentication token.</returns>
<remarks>To be added.</remarks>
</AcquireTokenAsync>
</members>
</docs>
1 change: 1 addition & 0 deletions src/Microsoft.Data.SqlClient.sln
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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) { }
}
}
Loading

0 comments on commit b5319c4

Please sign in to comment.