Find file History
BrianTJackett Feature/reorganize code base clean (#12)
* Making the first day exercise create a new user and demonstrate how to do a basic $filter query

* changing domain in program

* re-organized code

* seting up base console app folder

* reworked day 16 to use a helper class

* Rough skeleton template for a day exercise

* moved domain setting into the appsettings.json file

* updating example config in day 16

* Rough skeleton template for a day exercise

* pull changes from re-org into template

* planner and device code: fixing merge issues

* Fix typo and add clarity around template folder.

* Initial commit copy template folder

* Add LicenseHelper class, adapt main program

* Remove unused HttPClient

* Prepare content for publishing

Remove unused helper class, update README instructions, add screenshots, and clean up codebase.

* Update README.md
Latest commit b94787c Nov 15, 2018

README.md

Base Console Application Setup

Prerequisites

To complete this sample you need the following:

  • Visual Studio Code installed on your development machine. If you do not have Visual Studio Code, visit the previous link for download options. (Note: This tutorial was written with Visual Studio Code version 1.28.2. The steps in this guide may work with other versions, but that has not been tested.)
  • .Net Core SDK. (Note This tutorial was written with .Net Core SDK 2.1.403. The steps in this guide may work with other versions, but that has not been tested.)
  • C# extension for Visual Studio Code
  • Either a personal Microsoft account with a mailbox on Outlook.com, or a Microsoft work or school account.

If you don't have a Microsoft account, there are a couple of options to get a free account:

Step 1: Create a .Net Core Console Application

  1. Create a folder called ConsoleGraphTest for the console application.

    Note: For the purposes of this sample the project folder was named ConsoleGraphTest. If you choose a different folder name ensure that the namespace for files matches.

  2. Open the command line and navigate to this folder. Run the following command:

    dotnet new console
    
  3. Before moving on, install the following NuGet packages that you will use later.

    • Microsoft.Identity.Client
    • Microsoft.Graph
    • Microsoft.Extensions.Configuration
    • Microsoft.Extensions.Configuration.FileExtensions
    • Microsoft.Extensions.Configuration.Json

    Run the following commands to install these NuGet packages:

    dotnet add package Microsoft.Identity.Client --version 2.3.1-preview
    dotnet add package Microsoft.Graph
    dotnet add package Microsoft.Extensions.Configuration
    dotnet add package Microsoft.Extensions.Configuration.FileExtensions
    dotnet add package Microsoft.Extensions.Configuration.Json
    

Step 2: Register a web application with the new Azure AD Portal App Registration

In this exercise, you will create an Azure AD app registration using the new Azure AD Portal App Registrations UI (in preview as of the time of publish Nov 2018).

  1. Open a browser and navigate to the Azure AD Portal. Login using a personal account (aka: Microsoft Account) or Work or School Account with permissions to create app registrations.

    Note: If you do not have permissions to create app registrations contact your Azure AD domain administrators.

  2. Click Azure Active Directory from the left-hand navigation menu.

  3. Click App registrations (Preview) from the current blade navigation pane.

    Note: All information and example screenshots are using the preview versions of this registration portal and are subject to change. We will attempt to update this documentation to match after the new app registration UI is generally available (GA).

  4. Click New registration from the current blade content.

  5. On the Register an application page, specify the following values:

    • Name = .NET Core Graph Tutorial
    • Supported account types = <choose the value that applies to your needs>
    • Redirect URI

    Note: Ensure that the Redirect URI value is unique within your domain. This value can be changed at a later time and does not need to point to a hosted URI. If the example URI above is already used please choose a unique value.

    Screenshot of creating a new app in the Azure AD App Registration Portal website

    1. Copy the Redirect URI as you will need it later.
  6. On the .NET Core Graph Tutorial page, copy the Application (client) ID and Directory (tenant) ID as you will need both later.

    Screenshot of newly created application's ID

  7. Click Certificates & secrets from the current blade navigation pane.

    1. Click New client secret.

    2. On the Add a client secret dialog, specify the following values:

      • Description = Secret1
      • Expires = In 1 year
    3. Click Add.

      Screenshot of creating application client secret

    4. After the screen has updated with the newly created client secret copy the VALUE of the client secret as you will need it later.

      Important: This secret string is never shown again, so make sure you copy it now.

      Screenshot of newly created application's client secret

  8. Click API permissions from the current blade navigation pane.

    1. Click Add a permission from the current blade content.

    2. On the Request API permissions flyout select Microsoft Graph.

      Screenshot of selecting Microsoft Graph permission to add to app registration

    3. Select Application permissions.

    4. In the "Select permissions" search box type "User".

    5. Select User.Read.All from the filtered list.

      Screenshot of adding application permission for User.Read.All permission

    6. Click Add permissions at the bottom of flyout.

  9. Back on the API permissions content blade, click Grant admin consent for <name of tenant>.

    Screenshot of granting admin consent for newly added permission

    1. Click Yes.

Step 3: Extend the app for Azure AD Authentication

In this step you will extend the application from the previous step to support authentication with Azure AD. This is required to obtain the necessary OAuth access token to call the Microsoft Graph. In this step you will integrate the Microsoft Authentication Library library into the application.

  1. On the command line from Step 1, run the following command inside the project folder to open Visual Studio Code with the project folder opened:

    code .
    
  2. Add a file to the folder named appsettings.json with the following content:

    {
        "applicationId": "YOUR_APP_ID_HERE",
        "applicationSecret": "YOUR_APP_SECRET_HERE",
        "tenantId": "YOUR_TENANT_ID_HERE",
        "redirectUri": "YOUR_REDIRECT_URI_HERE",
        "domain": "YOUR_DOMAIN_HERE"
    }
  3. Edit appsettings.json and fill in the values obtained in previous step on the Azure AD Portal app registration UI:

    1. Replace YOUR_APP_ID_HERE with your application ID.
    2. Replace YOUR_APP_SECRET_HERE with your client secret (VALUE from Secret1 in previous steps).
    3. Replace YOUR_TENANT_ID_HERE with your tenant (domain) ID.
    4. Replace YOUR_REDIRECT_URI_HERE with your application redirect URI.
    5. Replace YOUR_DOMAIN_HERE with a vaild domain for your Azure Active Directory instance, e.g. contoso.onmicrosoft.com or contoso.com

Important: If you're using source control such as git, now would be a good time to exclude the appsettings.json file from source control to avoid inadvertently leaking your app ID and secret.

Create helper classes

  1. Create a new folder called Helpers.

  2. Create a new file in the Helpers folder called AuthHandler.cs.

  3. Replace the contents of AuthHandler.cs with the following code:

    using System;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;
    using Microsoft.Identity.Client;
    using Microsoft.Graph;
    using Microsoft.Extensions.Configuration;
    using System.Linq;
    using System.Threading;
    
    namespace ConsoleGraphTest
    {
        // This class allows an implementation of IAuthenticationProvider to be inserted into the DelegatingHandler
        // pipeline of an HttpClient instance.  In future versions of GraphSDK, many cross-cutting concernts will
        // be implemented as DelegatingHandlers.  This AuthHandler will come in the box.
        public class AuthHandler : DelegatingHandler {
            private IAuthenticationProvider _authenticationProvider;
    
            public AuthHandler(IAuthenticationProvider authenticationProvider, HttpMessageHandler innerHandler)
            {
                InnerHandler = innerHandler;
                _authenticationProvider = authenticationProvider;
            }
    
            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                await _authenticationProvider.AuthenticateRequestAsync(request);
                return await base.SendAsync(request,cancellationToken);
            }
        }
    }
  4. Create a new file in the Helpers folder called MsalAuthenticationProvider.cs

  5. Replace the contents of MsalAuthenticationProvider.cs with the following code:

    using System;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;
    using Microsoft.Identity.Client;
    using Microsoft.Graph;
    using Microsoft.Extensions.Configuration;
    using System.Linq;
    
    namespace ConsoleGraphTest
    {
        // This class encapsulates the details of getting a token from MSAL and exposes it via the
        // IAuthenticationProvider interface so that GraphServiceClient or AuthHandler can use it.
        // A significantly enhanced version of this class will in the future be available from
        // the GraphSDK team.  It will supports all the types of Client Application as defined by MSAL.
        public class MsalAuthenticationProvider : IAuthenticationProvider
        {
            private ConfidentialClientApplication _clientApplication;
            private string[] _scopes;
    
            public MsalAuthenticationProvider(ConfidentialClientApplication clientApplication, string[] scopes) {
                _clientApplication = clientApplication;
                _scopes = scopes;
            }
    
            /// <summary>
            /// Update HttpRequestMessage with credentials
            /// </summary>
            public async Task AuthenticateRequestAsync(HttpRequestMessage request)
            {
                var token = await GetTokenAsync();
                request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
            }
    
            /// <summary>
            /// Acquire Token
            /// </summary>
            public async Task<string> GetTokenAsync()
            {
                AuthenticationResult authResult = null;
                authResult = await _clientApplication.AcquireTokenForClientAsync(_scopes);
                return authResult.AccessToken;
            }
        }
    }

Step 4: Extend the app for Microsoft Graph

In this step you will incorporate the Microsoft Graph into the application. For this application, you will use the Microsoft Graph Client Library for .NET to make calls to Microsoft Graph.

Get user information from tenant

  1. Opening the Program.cs file. Add the following "using" statements to the top of the file.

    using System;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;
    using Microsoft.Identity.Client;
    using Microsoft.Graph;
    using Microsoft.Extensions.Configuration;
  2. Ensure that the namespace matches across the project. In this example we have used ConsoleGraphTest. Update the namespace accordingly if needed.

  3. Inside the Program class add static references to GraphServiceClient and HttpClient. These static variables can be used to instantiate the clients used to make calls against the Microsoft Graph.

    private static GraphServiceClient _graphServiceClient;
    private static HttpClient _httpClient;
  4. Inside the Program class add a new method LoadAppSettings with the following definition. This method retrieves the configuration values from a separate file. This allows updating the configuration (client Id, client secret, etc.) independently of the code itself. This is a general best practice when possible to separate configuration from code.

    private static IConfigurationRoot LoadAppSettings()
    {
        try
        {
            var config = new ConfigurationBuilder()
            .SetBasePath(System.IO.Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", false, true)
            .Build();
    
            // Validate required settings
            if (string.IsNullOrEmpty(config["applicationId"]) ||
                string.IsNullOrEmpty(config["applicationSecret"]) ||
                string.IsNullOrEmpty(config["redirectUri"]) ||
                string.IsNullOrEmpty(config["tenantId"]) ||
                string.IsNullOrEmpty(config["domain"]))
            {
                return null;
            }
    
            return config;
        }
        catch (System.IO.FileNotFoundException)
        {
            return null;
        }
    }
  5. Inside the Program class add a new method CreateAuthorizationProvider that will be used in later methods to instantiate the clients used for making calls against the Microsoft Graph. This method uses the configuration data with a ConfidentialClientApplication.

    private static IAuthenticationProvider CreateAuthorizationProvider(IConfigurationRoot config)
    {
        var clientId = config["applicationId"];
        var clientSecret = config["applicationSecret"];
        var redirectUri = config["redirectUri"];
        var authority = $"https://login.microsoftonline.com/{config["tenantId"]}/v2.0";
    
        List<string> scopes = new List<string>();
        scopes.Add("https://graph.microsoft.com/.default");
    
        var cca = new ConfidentialClientApplication(clientId, authority, redirectUri, new ClientCredential(clientSecret), null, null);
        return new MsalAuthenticationProvider(cca, scopes.ToArray());
    }
  6. Inside the Program class add a new method GetAuthenticatedGraphClient with the following definition. This method creates an instance of the GraphServiceClient from the static reference. The GraphServiceClient instance uses the configuration returned from previous method.

    private static GraphServiceClient GetAuthenticatedGraphClient(IConfigurationRoot config)
    {
        var authenticationProvider = CreateAuthorizationProvider(config);
        _graphServiceClient = new GraphServiceClient(authenticationProvider);
        return _graphServiceClient;
    }
  7. Inside the Program class add a new method GetAuthenticatedHTTPClient with the following definition. This method creates an instance of the HTTPClient from the static reference. The HTTPClient instance uses the configuration returned from previous method.

    private static HttpClient GetAuthenticatedHTTPClient(IConfigurationRoot config)
    {
        var authenticationProvider = CreateAuthorizationProvider(config);
        _httpClient = new HttpClient(new AuthHandler(authenticationProvider, new HttpClientHandler()));
        return _httpClient;
    }
  8. Inside the Main method add the following to load the configuration settings.

    var config = LoadAppSettings();
    if (null == config)
    {
        Console.WriteLine("Missing or invalid appsettings.json file. Please see README.md for configuration instructions.");
        return;
    }
  9. Continuing in the Main method add the following to get an authenticated instance of the GraphServiceClient and send a request to retrieve the first user from Users endpoint on the Microsoft Graph.

    //Query using Graph SDK (preferred when possible)
    GraphServiceClient graphClient = GetAuthenticatedGraphClient(config);
    List<QueryOption> options = new List<QueryOption>
    {
        new QueryOption("$top", "1")
    };
    
    var graphResult = graphClient.Users.Request(options).GetAsync().Result;
    Console.WriteLine("Graph SDK Result");
    Console.WriteLine(graphResult[0].DisplayName);
  10. Continuing in the Main method add the following to get an authenticated instance of the HttpClient and send a request to retrieve the first user from Users endpoint on the Microsoft Graph.

    //Direct query using HTTPClient (for beta endpoint calls or not available in Graph SDK)
    HttpClient httpClient = GetAuthenticatedHTTPClient(config);
    Uri Uri = new Uri("https://graph.microsoft.com/v1.0/users?$top=1");
    var httpResult = httpClient.GetStringAsync(Uri).Result;
    
    Console.WriteLine("HTTP Result");
    Console.WriteLine(httpResult);

This completes our first set of file edits and additions. Ensure all files are saved. In order to test the console application run the following commands from the command line:

dotnet build
dotnet run

Consider what this code is doing.

  • The GetAuthenticatedGraphClient function initializes a GraphServiceClient with an authentication provider that calls AcquireTokenForClientAsync.
  • In the Main function:
    • The graph endpoint that will be called is /v1.0/users/$top=1.
  • The HttpClient call with a manually constructed url and the GraphServiceClient sdk call are functionaly equivalent, which you choose to use in your applications will depend on your team practices, coding styles and target languages.