Condividi tramite


Getting started - Using Azure Active Directory (AAD) for authenticating automated clients (C#)

In this post, we will create an ASP.Net Web API as the service to be protected and a console application as the automated client consuming the service. We will implement three methods for the client to authenticate itself to Azure AD to get the claims (Certificate, Key and Credential), that it will then send to the Web API along with service. The Web API will authenticate the client based on the claims received. We will be implementing both the Web API and the client from empty visual studio projects. For implementing this authentication scenario, you need not be the administrator of the Azure Active Directory tenant, but must be a user of it.

This post contains the following sections:

  1. Registering Web API with Azure AD Tenant
  2. Creating Web API
  3. Registering Client with Azure AD Tenant 
  4. Creating client application
  5. Getting client claims on the server (Web API) side
  6. Removing certificate authentication for the client
  7. Using Keys instead of certificate to authenticate client
  8. Fetching Object ID for a certificate or key authenticated AD client
  9. Using username and password instead of certificate/key authentication
  10. Read more/ References

Prerequisites:

  • An Azure Active Directory tenant that you are a user of. 

                 We will be using an AD named RisingVentureDirectory and a user in it with username as ajay@risingventure.onmicrosoft.com.

  • Visual Studio 2013

1. Registering Web API with Azure AD Tenant

  1. Log into Azure management portal. Click on Active Directory in left navigation panel. Go to APPLICATIONS tab of your AD. Click on ADD at the bottom bar.

       

  2. Click on 'Add an application my organization is developing'.

  3. Enter name of your application (ex. RisingWebAPI). Select 'WEB APPLICATION AND/OR WEB API'. Click next.

  4. In Sign-on URL, enter URL for the Web API. This does not have to be a working URL. It can be the base URL for the Web API assigned by Visual Studio (e.g. https://localhost:10000). 

  5. In App ID URI, enter https://<AD_Tenant_FullName>/<Name_Of_WebAPI>. (e.g. https://risingventure.onmicrsoft.com/risingwebapi)

  6. Click OK mark to complete the registration.

  7. Go to CONFIGURE tab of the just registered application. Note the Client ID for it.

2. Creating Web API

  1. Create an ASP.Net project in Visual Studio and name it RisingWebAPI 

        

  2. Select Empty template and check WebAPI option. Click on OK to create the Web API.

       

  3. Open Web.config and add the following keys in App settings:

 <appSettings>
 <add key="Tenant" value="risingventure.onmicrosoft.com" />
 <add key="Audience" value="https://risingventure.onmicrosoft.com/risingwebapi" />
 <add key="ClientID" value="226c660b-fb73-46d5-99b2-c453b271a30d" />
 </appSettings>
 4. Install the following Nuget packages to the RisingWebAPI project :
  • Microsoft.Owin
  • Microsoft.Owin.Security.ActiveDirectory
  • Microsoft.Owin.Host.SystemWeb (This is for hosting the Web API in IIS/IIS express)

  5. Add a C# file to the project, named Startup.cs and put the following content into it:         

 using Owin;
 
 namespace RisingWebAPI
 {
 public partial class Startup
 {
 public void Configuration(IAppBuilder app)
 {
 ConfigureAuth(app);
 }
 }
 }

     Please note the class must be named 'Startup'. This class is automatically found and initialized by the new ASP.Net runtime and its 'Configuration' method is called by passing IAppBuilder object. Note that this should be in default namespace of the Web API project. You can find the default namespace from project properties. If it is not in default namespace, you must add a key in 'AppSettings' section of Web.Config, like: <add key="owin:AppStartup" value="[Namespace].Startup" />. If IAppBuilder interface is unrecognized in VS, re-install the Owin nuget package. We will add ConfigureAuth method next.

  6. Add another C# file to the folder App_Start in the Web API project and put the following content into it: 

 using System.Configuration;
 using Microsoft.Owin.Security.ActiveDirectory;
 using Owin;
 using System.IdentityModel.Tokens;
 
 namespace RisingWebAPI
 {
 public partial class Startup
 {
 public void ConfigureAuth(IAppBuilder app)
 {
 var tokenValidationParameter = new TokenValidationParameters();
 tokenValidationParameter.ValidAudience = ConfigurationManager.AppSettings["Audience"];
 app.UseWindowsAzureActiveDirectoryBearerAuthentication(
 new WindowsAzureActiveDirectoryBearerAuthenticationOptions
 {
 TokenValidationParameters = tokenValidationParameter,
 Tenant = ConfigurationManager.AppSettings["Tenant"]
 });
 }
 }
 }

   7. Add a controller named GetdataController to the Web API project. Select 'Web API 2 Controller - Empty' from Add Scaffold window. Put the following content in it:

 using System.Web.Http;
 
 namespace RisingWebAPI.Controllers
 {
 [Authorize]
 public class GetdataController : ApiController
 {
 public string Get()
 {
 return "Hello";
 }
 }
 }

  8. Save and build the solution. Run the solution by pressing F5. If you try to access the Getdata controller (for example by hitting https://localhost:10000/api/getdata from a browser), it will give authorization error. This is because the WebAPI is now configured for Azure AD authentication and we require the Getdata controller to be authenticated ([Authorize] attribute). If you add any other controller without [Authorize] attribute, it should still be accessible. Next we will create the client that will get the authentication token from Azure AD tenant and pass to Web API.

 

3. Registering Client with Azure AD Tenant 

Clients can authenticate to Azure AD tenant through three ways : certificate credential, client keys and username/password credential. In this section we will cover certificate credential. Other two would be discussed in later sections. Certificate and client keys create an application identity while username/password will have an identity of a user (whose credential is used to authenticate).

  1. Go to Azure AD tenant as mentioned earlier on Azure management portal.

  2. On the APPLICATIONS tab, click ADD from the bottom bar.

  3. Click on 'Add an application my organization is developing'.

  4. Enter meaningful name for the application (e.g. RisingAPIClient).

  5. Select 'WEB APPLICATION AND/OR WEB API'. It does not matter if our client application is a native console app. Go next.

  6. For Sign-on URL and App ID URI, enter any valid URI. (e.g. https://risingapiclient/ for both fields). This does not have to be an existing/working URL, however, App ID URI must be unique within your AD. Click OK mark to register the application.

  7. Go to the CONFIGURE tab of the just added application and copy the client ID. Also give the client application access to the Web API. In the CONFIGURE, scroll to the bottom and click on 'Add application'. 

       

 

 

      Select 'All Apps' from the drop down menu from the pop up window.

         

 

       Search for the 'RisingWebAPI' and select it. Click ok mark then.

           

 

      Give permission to 'Access RisingWebAPI' and click save.

        

  8. Run the following command to create a self-signed certificate on your computer from developer command prompt:

        makecert -r -pe -n "CN=RisingAPIClient" -ss My -len 2048 RisingAPIClient.cer

  9. Certificate need to be associated with the registered client application in Azure AD. This can be done using Azure AD module for Powershell. Install the module from this link.

  10. After you have installed the module, run the following commands from a Powershell command window:

  • PS C:\windows\system32> connect-msolservice

              (Enter your username and password for the Azure AD user, when prompted. For example, ajay@risingventure.onmicrosoft.com , p@$$w0rd . Note that the user must be part of this specific AD and should have username like *@risingventure.onmicrosoft.com. In azure portal, the account that creates the Azure AD is automatically added as an user to the newly created AD. This account may not work for adding certificate credentials for applications in newly created AD. Also, note that the user account which registered the client application on AD or a global administrator of the AD will have permissions to add certificate credentials.)

  • PS C:\windows\system32> $cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate
  • PS C:\windows\system32> $cer.Import("E:\Certs\RisingAPIClient.cer")     (Path to the created certificate)
  • PS C:\windows\system32> $binCert = $cer.GetRawCertData()
  • PS C:\windows\system32> $credValue = [System.Convert]::ToBase64String($binCert)
  • PS C:\windows\system32> New-MsolServicePrincipalCredential -AppPrincipalId "<client ID>" -Type asymmetric -Value $credValue -Usage verify

          (clientId = f5c69304-8c94-48e5-9dac-d9742ca021aa for our client application). If you get Access Denied error, then check if the same user created the client application on Azure portal that you used to login in the first powershell command of 'connect-msolservice'. Otherwise, get these commands executed under a global administrator account.

  11. Install the certificate created in step 8 on the machine where you intend to run the client from. It should be installed in the current user personal store. Note its thumbprint.

4. Creating client application

  1. Create a console application in Visual Studio.

  2. Install nuget package 'Active Directory Authentication Library'.

  3. Add reference to .Net framework assemblies - System.Net, System.Net.Http, System.Configuration.

  4. Add the following configuration to the App.Config:

  <add key="AADInstance" value="https://login.windows.net/{0}" />
 <add key="Tenant" value="risingventure.onmicrosoft.com" /> <!-- Azure AD tenant -->
 <add key="ClientId" value="f5c69304-8c94-48e5-9dac-d9742ca021aa" /> <!-- Client ID of the registered client application on AD -->
 <add key="CertThumbprint" value="e21bcf7d735077324f8cf5cf9b21b81cf852f6a7" /> <!-- Thumbprint of the certificate created in last section -->
 <add key="ApiResourceId" value="https://risingventure.onmicrsoft.com/risingwebapi" /> <!-- APP ID URI of the Web API registered with AD -->
 <add key="ApiBaseAddress" value="https://localhost:10000/" /> <!-- Actual URL where Web API is hosted -->
 5. Put the following code to the program.cs:
 using Microsoft.IdentityModel.Clients.ActiveDirectory;
 using System;
 using System.Configuration;
 using System.Globalization;
 using System.Net.Http;
 using System.Net.Http.Headers;
 using System.Security.Cryptography.X509Certificates;
 using System.Threading.Tasks;
 
 namespace RisingAPIClient
 {
 class Program
 {
 private static string aadInstance = ConfigurationManager.AppSettings["AADInstance"];
 private static string tenant = ConfigurationManager.AppSettings["Tenant"];
 private static string clientId = ConfigurationManager.AppSettings["ClientId"];
 private static string certThumbprint = ConfigurationManager.AppSettings["CertThumbprint"];
 static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
 private static string apiResourceId = ConfigurationManager.AppSettings["ApiResourceId"];
 private static string apiBaseAddress = ConfigurationManager.AppSettings["ApiBaseAddress"];
 private static HttpClient httpClient = new HttpClient();
 private static AuthenticationContext authContext = null;
 private static ClientAssertionCertificate certCred = null; 
 
 static void Main(string[] args)
 {
 bool retValue = CallWebAPI().Result;
 Console.WriteLine("Completed");
 Console.ReadKey();
 }
 
 static async Task<bool> CallWebAPI()
 {
 authContext = new AuthenticationContext(authority);
 X509Certificate2 cert = null;
 X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
 try
 {
 store.Open(OpenFlags.ReadOnly);
 cert = store.Certificates.Find(X509FindType.FindByThumbprint, certThumbprint, false)[0];
 }
 finally
 {
 store.Close();
 }
 certCred = new ClientAssertionCertificate(clientId, cert);
 AuthenticationResult result = null;
 try
 { 
 result = await authContext.AcquireTokenAsync(apiResourceId, certCred); 
 }
 catch (AdalException)
 {
 //May be retry
 }
 httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
 HttpResponseMessage response = null;
 try
 {
 response = await httpClient.GetAsync(apiBaseAddress + "api/getdata");
 }
 catch (Exception ex)
 {
 Console.WriteLine(ex.Message);
 }
 if (response.IsSuccessStatusCode)
 {
 string s = await response.Content.ReadAsStringAsync();
 Console.WriteLine(s);
 }
 else
 {
 Console.WriteLine("ERROR :" + response.StatusCode);
 }
 return true;
 }
 }
 }
 6. Run the Web API solution first and then run the client solution. 
  The client should be able to access the WebAPI and fetach the data.

5. Getting client claims on the server (Web API) side

Client details can be fetched on the server side by from the current claims principals. For example you can get the Object ID of the client using the following code:

 Claim clientClaim = ClaimsPrincipal.Current.FindFirst("https://schemas.microsoft.com/identity/claims/objectidentifier"); //You may have to include System.Security.Claims namespace. 
 string objectId = clientClaim.Value;

Object ID is an immutable and globally unique identifier and can be used for authorization purposes. Read more about it here.

You can also get other claims:

   

6. Removing certificate authentication for the client

  You should have Azure AD module for powershell installed as described earlier.

  1. Run the following command in powershell to get the Key Id:

            Get-MsolServicePrincipalCredential –ServicePrincipalName “<App ID URI of client >” -ReturnKeyValues 0        

(App ID URI of client e.g. https://risingapiclient/)

   You will get the following output:

                      

    Note the KeyId.

  2. Next run the following command:

      Remove-MsolServicePrincipalCredential -KeyIds @("5ab1d398-a42f-4f99-9981-cca0c985ff67")

              -ServicePrincipalName "https://risingapiclient/"

7. Using Keys instead of certificate to authenticate client

  1. Sign in to Azure portal. Go to AD and go to 'CONFIGURE' tab of the client application.

  2. Under 'Keys' section, select the duration of the validity of the key. Next click on save at the bottom to make the key visible.

          

  3. Copy the key. Note that you would not be able to retrieve the key after you leave the page.

  4. Add the following app setting to App.config file of client project:       

 <add key="AppKey" value="<Key retrieved from portal>"/> 

  5. Do the following changes to program.cs:

       In place of 

 X509Certificate2 cert = null;
 X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
 try
 {
 store.Open(OpenFlags.ReadOnly);
 cert = store.Certificates.Find(X509FindType.FindByThumbprint, certThumbprint, false)[0];
 }
 finally
 {
 store.Close();
 }
 certCred = new ClientAssertionCertificate(clientId, cert);

       Put

  string appKey = ConfigurationManager.AppSettings["AppKey"];
 ClientCredential clientCredential = new ClientCredential(clientId, appKey);

       And, in place of              

  result = await authContext.AcquireTokenAsync(apiResourceId, certCred);              

       put             

  result = authContext.AcquireToken(apiResourceId, clientCredential);

  6. Now run the client again. It should be able to access the Web API.

8. Fetching Object ID for a certificate or key authenticated AD client

  You may be required to get the Object ID of a client that uses certificate or key authentication to AD to on-board to some custom authorization logic.

  For this, you should have the Azure AD module for powershell installed as described earlier. Run the following commands to get the Object ID:

  •     connect-msolservice    

 (Enter AD username and password as before)

  • Get-MsolServicePrincipal -AppPrincipalId '<Client ID>'

 (Client ID of the registered application, e.g. f5c69304-8c94-48e5-9dac-d9742ca021aa)

   It should give you the ObjectId as below:

      

9. Using username and password instead of certificate/key authentication

  An actual user can authenticate to AD rather than an application identity. However for this to work the registering of client on the Ad is little different. We will create a new client in AD.

  1. Refer to the above section of 'Registering Client with Azure AD Tenant' and follow steps 1-3.

  2. Give the name of the application as 'RisingAPINativeClient'. Select 'NATIVE CLIENT APPLICATION'. Click next.

  3. Enter any valid URI (e.g. https://nativeclient/) in the 'Redirect URI' field and click OK mark to create the application.

  4. Go to configure tab and copy the client id. Also give the application access to Web API by following step 7 from section 'Registering Client with Azure AD Tenant' .

  5. In the client project, change the config value of client id to be of the newly created client application.

  6. Do the following change to the program.cs:

       Replace the line        

  result = authContext.AcquireToken(apiResourceId, clientCredential); 

       With          

 result = authContext.AcquireToken(apiResourceId, clientId, new Uri("https://nativeclient/"), PromptBehavior.Always); 

  7. Run the client application now. You will get a prompt for username and password.

                

  8. Sign in with an AD account. The Web API will accessible through this way also.

  9. You can get the get the user details the same way on the server side from claims principals. This time there would be more claims pertaining to user details. If you want to fetch the user details for on-boarding purposes, you can get it through Graph API

10. Read more/ References