VB.NET – Use MSAL.Net in a Console Application to Authenticate to Azure

At one point in recent history, VB.Net was one of the most popular development languages around. In fact, there are many legacy applications written in VB.Net and even still, VB.Net is still in the top 10 languages ( source: https://www.techworm.net/2018/02/popular-programming-languages-2018-according-tiobe-pypl.html ).  I will show a simple console application using VB.Net to authenticate using MSAL.Net

Of course, the first step, as in everything Azure, starts with setting up an app registration to use for Authentication. For this demonstration, I created a single tenant app registration with the following Redirect URI configured ( urn:ietf:wg:oauth:2.0:oob ):

There is nothing else special about this app registration.

Now, for the code. To start with, you must install the Microsoft.Identity.Client from the Nuget package manager. I will present to you sample VB.Net code that performs authentication synchronously, then asynchronously.

Imports Microsoft.Identity.Client

Module Module1

    Private _accessToken As String = String.Empty
    Private Const client_id As String = "{client_id}" '<-- enter the client_id guid here
    Private Const tenant_id As String = "{tenant_id}" '<-- enter either your tenant id here
    Private authority As String = $"https://login.microsoftonline.com/{tenant_id}"
    Private scopes As New List(Of String)

    Sub Main()

        scopes.Add($"{client_id}/.default")

        Console.WriteLine("Starting Synchronous Sample...")

        SyncSample()

        Console.WriteLine($"{Environment.NewLine}End Synchronous Sample...{Environment.NewLine}Start Asynchronous Sample...")

        AsyncSample()

        Console.WriteLine($"{Environment.NewLine}End Asynchronous Sample.{Environment.NewLine}Press any key to close...")
        Console.ReadKey()

    End Sub

#Region "Synchronous Code"


    Private Sub SyncSample()
        If Login() Then
            Console.WriteLine(_accessToken)
        Else
            Console.WriteLine("Not Authorized")
        End If
    End Sub

    Private Function Login() As Boolean

        Dim publicClientApp As IPublicClientApplication
        publicClientApp = PublicClientApplicationBuilder.Create(client_id).WithAuthority(authority).Build()

        Dim accounts As IEnumerable(Of IAccount) = publicClientApp.GetAccountsAsync().Result()
        Dim firstAccount As IAccount = accounts.FirstOrDefault()
        Dim authResult As AuthenticationResult

        Try
            authResult = publicClientApp.AcquireTokenSilent(scopes, firstAccount).ExecuteAsync().Result()
        Catch e As MsalUiRequiredException
            Try
                authResult = publicClientApp.AcquireTokenInteractive(scopes).ExecuteAsync().Result()
            Catch ex As Exception
                'user cancelled
                Return False
            End Try
        Catch ex As Exception
            Console.WriteLine($"Auth Exception: {ex.Message}")
            Return False
        End Try

        _accessToken = authResult.AccessToken

        Return True

    End Function

#End Region

#Region "Asynchronous Code"

    Private Sub AsyncSample()

        Dim task As Task(Of Boolean) = LoginTask()

        If task.Result() Then
            Console.WriteLine(_accessToken)
        Else
            Console.WriteLine("Not Authorized")
        End If
    End Sub

    Private Async Function LoginTask() As Task(Of Boolean)
        Dim publicClientApp As IPublicClientApplication
        publicClientApp = PublicClientApplicationBuilder.Create(client_id).WithAuthority(authority).Build()

        Dim accounts As IEnumerable(Of IAccount) = Await publicClientApp.GetAccountsAsync()
        Dim firstAccount As IAccount = accounts.FirstOrDefault()
        Dim authResult As AuthenticationResult

        Dim tryInteractive As Boolean = False

        Try
            authResult = Await publicClientApp.AcquireTokenSilent(scopes, firstAccount).ExecuteAsync()
            _accessToken = authResult.AccessToken
        Catch e As MsalUiRequiredException
            tryInteractive = True
        End Try

        If tryInteractive Then
            Try
                authResult = Await publicClientApp.AcquireTokenInteractive(scopes).ExecuteAsync()
                _accessToken = authResult.AccessToken
            Catch ex As Exception
                Return False
            End Try
        End If

        Return _accessToken <> String.Empty

    End Function

#End Region

End Module

 

Although there are quite a few differences from our C# samples, we can still leverage MSAL in our VB.Net code as well with some minor changes.

UPDATE: 06/18/2020
To clear up some confusion about the redirect URI, please try selecting the default MSAL redirect from the portal that is created when you create the app registration and after selecting that by clicking the checkbox, click the save button:

Then, modify your code to add the .WithRedirectUri parameter like so:
publicClientApp = PublicClientApplicationBuilder.Create(client_id).WithAuthority(authority).WithRedirectUri(“msal28a00d08-ba05-4015-a269-9d0546e850b9://auth”).Build()

The redirect URI I had in this post, I obtained to see what the default was sent during authentication by running fiddler, in an attempt to make this more universal for everyone, which apparently isn’t the case šŸ™‚

Troubleshooting ASP.NET Core Azure AD-protected Web API Authorization issues

There is plenty of content on the internet that shows you how to use ASP.NET Core Authentication pipeline to protect a Web API with Azure Active Directory.  This is all working great except when your Web API returns a 401 Unauthorized error without providing much information.  You can end up spending a lot of time, pulling your hair out trying to figure out what can go wrong even though you (or at least believe) have followed every step of the documentation, implemented all the best practices and guidelines.  Hopefully, this blog can help alleviate that pain for you.  I will build the content here on top of my previous blog post which discusses how you can protect your Web API with Azure Active Directory.

Letā€™s dig inā€¦

When you use the [Authorize] attribute to protect your web API controller as followed

    
[Authorize]
public class MyController : ControllerBase
{
    ...
}

or to protect an action in the controller

public class MyController : ControllerBase
{
   [Authorize]
   public ActionResult<string> Get(int id)
   {
       return "value";
   }
   ...
}

It expects the request has a valid Bearer token presented in the ā€œAuthorization: Bearer eyā€¦ā€ HTTP Request header.  The controller returns a 401 Unauthorized response when the request either does not have an “Authorization Bearer token” header or the request contains an invalid Bearer token (the token is expired, the token is for a different resource, or the token’s claims do not satisfy at least one of the applicationā€™s token validation criteria as defined in the JwtBearerOptions’s TokenValidationParameters class).

The JwtBearerEvents class has the following callback properties (invoked in the following order) that can help us debug these 401 Access Denied or UnAuthorization issues:

  1. OnMessageRecieved ā€“ gets called first for every request
  2. OnAuthenticationFailed ā€“ gets called when the token does not pass the applicationā€™s token validation criteria.
  3. OnChallenge ā€“ this gets called last before a 401 response is returned

Note:  In some scenarios, the OnAuthenticationFailed method may not be invoked, for example:  the request does not have any bearer token or it contains a malformed token (invalid jwt token format)

We will use the above callbacks to build our own custom error message to return to the client.

Note:  the sample code in this blog is only meant for Proof of Concept and can review PII information only for debugging local development effort so you should be cautious not to use in production environment.

Implement the following code:

  1. Enable PII logging (itā€™s off by default) in the Configure method in Startup.cs file:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }
    // turn on PII logging
    Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;

    app.UseHttpsRedirection();
    app.UseAuthentication();
    app.UseMvc();
}        
  1. Create a method called FlattenException to format our Exception Message
public static string FlattenException(Exception exception)
{
    var stringBuilder = new StringBuilder();
    while (exception != null)
    {
        stringBuilder.AppendLine(exception.Message);
        stringBuilder.AppendLine(exception.StackTrace);
        exception = exception.InnerException;
    }
    return stringBuilder.ToString();
}
  1. Implement the above callbacks in the ConfigureServices method in Startup.cs file
public void ConfigureServices(IServiceCollection services)
{
....
    .AddJwtBearer(options =>
    {
        options.Authority = "https://login.microsoftonline.com/<Tenant>.onmicrosoft.com";
        // if you intend to validate only one audience for the access token, you can use options.Audience instead of
        // using options.TokenValidationParameters which allow for more customization.
        // options.Audience = "10e569bc5-4c43-419e-971b-7c37112adf691";

        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
        {
            ValidAudiences = new List<string> { "<Application ID URI>", "10e569bc5-4c43-419e-971b-7c37112adf691" },
            ValidIssuers = new List<string> { "https://sts.windows.net/<Directory ID>/", "https://sts.windows.net/<Directory ID>/v2.0" }
        };
                
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = ctx =>
            {
                ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
                message += "From OnAuthenticationFailed:\n";
                message += FlattenException(ctx.Exception);
                return Task.CompletedTask;
            },

            OnChallenge = ctx =>
            {
                message += "From OnChallenge:\n";
                ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
                ctx.Response.ContentType = "text/plain";
                return ctx.Response.WriteAsync(message);
            },

            OnMessageReceived = ctx =>
            {
                message = "From OnMessageReceived:\n";
                ctx.Request.Headers.TryGetValue("Authorization", out var BearerToken);
                if (BearerToken.Count == 0)
                    BearerToken = "no Bearer token sent\n";
                message += "Authorization Header sent: " + BearerToken + "\n";
                return Task.CompletedTask;
            },

            OnTokenValidated = ctx =>
            {
                Debug.WriteLine("token: " + ctx.SecurityToken.ToString());
                return Task.CompletedTask;
            }
        };
    });
...
}

For completeness, I have also implemented the OnTokenValidated property to print out the token claims. This method is invoked when the authentication is successful.

Here is my entire Startup.cs file

[gist id=”90b646e2fedb0a446522d5e0076dddf7″ file=”Startup.cs”]

The result…

The above implementation should result in 401 Error message with some output as followed

or the following

Walkthrough: How to protect an ASP.NET Core Web API application with Azure AD

Pre-requisite:

The ASP.NET Core Web API project in this tutorial uses Visual Studio 2017 with .Net Core runtime version 2.2

Application Registration:

We will need to create an App Registration for the web API and an App Registration for the client app calling the web API in Azure Active Directory.

Web API:

From the Azure portal, navigate to the Azure Active Directory blade -> App registrations -> New registration to create an app registration.

For the account type, I choose the first option ‘accounts in this organizational directory only’ for simplicity.

Take note of the Application ID as you will need it later for the web API app

In the ‘Expose an API’ blade make sure you add a scope and set the ‘Application ID URI’ field. Take note of this as we will need it for the API App and configuring Postman later

Client App:

Use the same step as above to create a new app registration for the client app. There is no need to configure the ‘Application ID URI’ in the ‘Expose an API’ blade. Again take note of the Application ID as you will need it later to configure Postman

In the API permissions blade, click on ‘Add a permission’ to add the permission for the web API we created earlier. You also need to click on ‘Grant admin consent’ button to grant admin consent for the web API permission. This step is important since we will be using Postman to get an access token for the web API using client credentials grant flow, which requires the permission to be admin consented ahead of time.

In the ‘Certificates & secrets’ blade, click on the ‘New client secret’ button to create a new secret. Take note of this secret value as this is only displayed only once upon creation. Once you navigate away from this page, the secret value will not be shown.

Our web API project:

Using Visual Studio 2017, create a new ASP.NET Core Web Application project under Visual C#

In the next wizard, select API for the project type. As mentioned above, I am using ASP.NET Core 2.2 version and ‘No Authentication’ configured initially.

Make the following changes in the Startup.cs file:

  1. add the statement using Microsoft.AspNetCore.Authentication.JwtBearer; at the top so it should look similar to this:

using System.Collections.Generic;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

  1. In the ConfigureServices function, add the following JWT Bearer Authentication code at the beginning so the method looks like the following.Ā  You can get the Directory ID on the Application blade and the Tenant name in the Azure Active Directory’s Overview blade.Ā  For a complete list of TokenValidationParameters properties see https://docs.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.tokens.tokenvalidationparameters?view=azure-dotnetĀ 
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
   services.AddAuthentication(sharedoptions =>
   {
      sharedoptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
   })
   .AddJwtBearer(options =>
   {
   // Example: ā€œhttps://login.microsoftonline.com/contoso.onmicrosoft.comā€
      options.Authority = 'https://login.microsoftonline.com/<tenant name> or <Directory ID>';
   /* if you intend to validate only one audience for the access token, you can use options.Audience instead of using options.TokenValidationParameters which allow for more customization. */
// the Audience field here is typically your web API's Application ID // options.Audience = ā€œ10e569bc5-4c43-419e-971b-7c37112adf691ā€; options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidAudiences = new List<string> { 'https://contoso.onmicrosoft.com/TodoListService', '10e569bc5-4c43-419e-971b-7c37112adf691' }, // set the following issuers if you want to support both V1 and V2 issuers ValidIssuers = new List<string> { 'https://sts.windows.net/<Directory ID>/', 'https://login.microsoftonline.com/<Directory ID>/v2.0' } }; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
  1. In the Configure method add app.UseAuthentication(); right above app.useMvc() line so the method would look like this:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   if (env.IsDevelopment())
   {
      app.UseDeveloperExceptionPage();
   }
   else
   {
       // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
      app.UseHsts();
   }
      app.UseHttpsRedirection();
      app.UseAuthentication();
      app.UseMvc();
}
  1. Here is what my complete Startup.cs file looks like with the above changes.
[gist id=”76f3b777cac79a8544f455aa10396a30″ file=”Startup.cs”]
  1. In the ValuesController.cs file in the Controller folder add the following changes:
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace CoreWebAPIAAD.Controllers
{
   [Route("api/[controller]")]
   [ApiController]
   [Authorize]
   public class ValuesController : ControllerBase
   {
      ā€¦.

By adding the AuthorizeAttribute [Authorize] to the class ValuesController, we require authentication when invoking any method in this controller. In the example below we will use Postman to invoke the Get method with code below:

// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
   return new string[] { "Value 1", "Value 2"};
}

Rebuild and run the solution project. By default it will open a browser and navigate to https://localhost:44333/api/values showing a blank page. If you look at this request in a network trace like Fiddler or Network tab in the browser’s developer tool, this request results in a 401 response since the request is not authenticated.

Testing our web API app with Postman

We will use Postman to request an access token to our web API app using the client credentials grant flow. This technique is demonstrated in this post.

Client ID is the application ID of the client app. Client Secret is the secret created above for the client app. For scope, use web API’s “<Application ID URI>/.default” (for example https://contoso.onmicrosoft.com/TodoListService/.default). The Application ID URI will show up in the access token’s ‘aud’ claim.

Using that access token we should be able to call our web API successfully:

AADSTS650056: Misconfigured application. This could be due to one of the following: The client has not listed any permissions for ‘AAD Graph’ in the requested permissions in the client’s application registration.

Let’s get startedā€¦

You are getting the following similar messageā€¦

AADSTS650056: Misconfigured application. This could be due to one of the following: The client has not listed any permissions for ‘AAD Graph’ in the requested permissions in the client’s application registration. Or, The admin has not consented in the tenant. Or, Check the application identifier in the request to ensure it matches the configured client application identifier. Please contact your admin to fix the configuration or consent on behalf of the tenant.


Ensure you are accessing the application from the sign-in address provided by the application owner. Otherwise meaning, sign in to the application through its normal process. In most cases this will auto-resolve naturally. If it doesn’t, then this post can help troubleshoot and resolve it.


If your organization owns the application (meaing the application registration is in your organization)ā€¦

At minimum, we do recommended the User.Read or openid delegated permission from Microsoft Graph is added.

Ensure the application and all of its permissions are consented to. You can verify this by looking at the Status column of the Application registration within API Permissions. For example, this User.Read permission has not been consented to yetā€¦

If it is successfully consented, then it will look something like thisā€¦

For application owners developing a Multi-tenant application, you will make your customers lives much easier when consenting to your application when you can add the User.Read delegated permission in addition to your other permission requirements.

In some scenarios, the application might be third-party however it may be registered in your organization. Confirm if this application is listed in your App registrations (Not Enterprise applications).

If you continue to see this error message. Then you may need to build the consent URL described below.


If your organization is not the application owner and using it as a third-party applicationā€¦

If you’re the Global/Company administrator, you should see the consent screen. Make sure you check the box for “Consent on behalf of your organization

If you don’t see the consent screen, delete the Enterprise application, and try again.

For exampleā€¦

If you continue to see this error message. Then you may need to build the consent URL described below.


Manually build the consent URL to be used.

If the application is designed to access a specific resource, you may not be able to use the Consent buttons from the Azure portal, you will need to manually generate your own consent URL and use this.

When using our authorization V1 endpoint, it will look something like this…

https://login.microsoftonline.com/{Tenant-Id}/oauth2/authorize
?response_type=code
&client_id={App-Id}
&resource={App-Uri-Id}
&scope=openid
&prompt=consent

For example…

https://login.microsoftonline.com/contoso.onmicrosoft.com/oauth2/authorize
?response_type=code
&client_id=044abcc4-914c-4444-9c3f-48cc3140b6b4
&resource=https://vault.azure.net/
&scope=openid
&prompt=consent

When using our authorization V2 endpoint, it will look something like this…

https://login.microsoftonline.com/{Tenant-Id}/oauth2/v2.0/authorize
?response_type=code
&client_id={App-Id}
&scope=openid+{App-Uri-Id}/{Scope-Name}
&prompt=consent

For example…

https://login.microsoftonline.com/contoso.onmicrosoft.com/oauth2/v2.0/authorize
?response_type=code
&client_id=044abcc4-914c-4444-9c3f-48cc3140b6b4
&scope=openid+https://vault.azure.net/user_impersonation
&prompt=consent

If the application is accessing itself for the resource, then the {App-Id} and {App-Uri-Id} will be the same.

You will need to get the {App-Id} and the {App-Uri-Id} from the application owner. {Tenant-Id} will be your tenant identifier. This will either be yourdomain.onmicrosoft.com or your directory IDā€¦

Why am I getting a Login Request after initial login using iOS MSAL?

Does your app keep asking you to login after initial login when implementing iOS MSAL? Did you implement this in your Android without any issues? Did you look over all your code a million times and your configurations look correct but still cannot figure out why this is happening? Well, you are in luck because we go over why this happens and how to fix it!

SCENARIO

Using MSAL SDK for implementing mobile authentication in iOS app following this tutorial.

This sample uses the Microsoft Authentication library (MSAL) to implement Authentication. MSAL will automatically renew tokens, deliver single sign-on (SSO) between other apps on the device, and manage the Account(s). The key thing to note here is in order for SSO to work, tokens need to be shared between apps. This requires a token cache, or broker application, such as Microsoft Authenticator for iOS.

Expected Behavior:
User does not get prompted to login since user already logged in using MSAL

Actual Behavior:
Received login request

ISSUE

You may have your web browsers configured in a way that does not allow cookie sharing.

A web browser is required for interactive authentication. On iOS, the Microsoft Authentication Library (MSAL) uses the system web browser by default to do interactive authentication to sign in users. Using the system browser has the advantage of sharing the Single Sign ON (SSO) state with other applications and with web applications.

Since the system browser is the default, you may have opted to customize this configuration in the process by redirecting to one of the following:

For iOS only: For iOS and macOS:
ASWebAuthenticationSession
SFAuthenticationSession
SFSafariViewController
WKWebView

All of that is completely fine but you have to set this up in a way that enables cookie sharing.

RESOLUTION

You can use one of the following combinations in order to allow cookie sharing:

  • ASWebAuthenticationSession in MSAL + openURL in Safari browser (not SafariViewController, the full browser)
  • SFSafariViewController in MSAL + SFSafariViewController in your app
  • WKWebView in MSAL + WKWebView in your app

See here for additional details on customizing webviews and browsers.

NOTE

For Xamarin.iOS there are several considerations to take that are separate from the issue described in this blog post including enabling token caching and using Microsoft Authenticator. You can find more information on how to achieve this here.

How to inject custom data into the ā€˜stateā€™ parameter in an OpenID Connect MVC Application

It’s often desirable for an Azure Active Directory (Azure AD)- integrated application to maintain application state when sending request to Azure AD for login. The recommended way to achieve this is to use the ‘state’ parameter as defined in the OpenID Connect standards. Also mentioned in our documentation, the ‘state’ parameter is used for both preventing cross-site request forgery attacks and to maintain user’s state before authentication request occurs:

For an ASP.NET or ASP.NET CORE web application using OpenID Connect OWIN middleware, the ‘state’ parameter is maintained automatically by the middleware when sending out an authentication request as followed.

GET https://contoso.b2clogin.com/contoso.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1a_signup_signin
&client_id=<Application ID>
&redirect_uri=<Some redirect URL>
&response_mode=form_post
&response_type=id_token
&scope=openid
&state=OpenIdConnect.AuthenticationProperties%3dgAAAALy6…i
&nonce=defaultNonce

Ā 

Upon receiving the response from Azure AD, the middleware takes care of validating the ‘state’ parameter to prevent cross-site forgery attack. Because this work is done automatically by the middleware framework, this begs the question: How can application developers still utilize this same ‘state’ parameter to maintain user state without compromising the middleware’s security feature?

The way to do this isā€¦

The OpenID Connect OWIN middleware use .Net framework’s Data Protection API to encrypt the value stored in the ‘state’ parameter. Thinking along the same line we can use the following code in OpenIdConnectNotifications’s RedirectToIdentityProvider event to inject custom data into the ‘state’ parameter:

var stateQueryString = notification.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.Add("MyData", "123");
notification.ProtocolMessage.State = stateQueryString[0] + "=" + notification.Options.StateDataFormat.Protect(state);

And we can use the following code to read our custom data back in the AuthenticationFailed event, MessageReceived event, or at any other relevant place in the code after we receive a response from Azure AD:

string mycustomparameter;
var protectedState = notification.ProtocolMessage.State.Split('=')[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.TryGetValue("MyData", out mycustomparameter);

Reference:

https://stackoverflow.com/questions/37489964/custom-parameter-with-microsoft-owin-security-openidconnect-and-azuread-v-2-0-en/37520329#37520329

Setup Postman to call Microsoft Graph using a Client Credentials Grant Access Token from the v2 endpoint

To use the V1 endpoint, please refer to this post.  Our documentation for the client credentials grant type can be found here.

You can setup postman to make a client_credentials grant flow to obtain an access token and make a graph call ( or any other call that supports application permissions ). This is very useful for testing code you plan to run as a script or in a Daemon application where you do not want user interaction.

The first thing you need is an app registration in your Azure Tenant to perform this flow. Create a new application registration:

Note the application id as you will need it later ā€“ this is the client_id used in the setup for postman:

Click on the API permissions and assign application permissions. By default, Microsoft Graph is already selected with a delegated permission of User.Read. I will leave that there to demonstrate that permission will not be present in the access token when performing this flow. Lets add an application permission for Microsoft Graph of Group.Read.All :

Once added, you will now see your permission listed however, all application permissions always require a tenant admin to consent to these permissions:

Grant admin consent:

Next, the client_credentials flow requires a client secret. Go to the Certificates and Secrets blade and create a new client secret:

The value is only shown one time so be sure to copy it to the clipboard with the copy to clipboard button and store that somewhere safe. You don’t want that secret to get out!

The first method I will demonstrate to you for obtaining an access token with Postman is through the Authorization UI. Start a new request, then click on the Authorization tab and select OAuth 2.0 from the drop-down type list:

To configure the flow, select Client Credentials from the Grant Type drop-down box then plug in your values for the settings it requests. For the scope, I am using https://graph.microsoft.com/.default which will pull the default values configured for Microsoft Graph from the app registration ā€“ in this case, we are expecting the groups.read.all permission. Use https://login.microsoftonline.com/{your-tenant}/oauth2/v2.0/token/ for the Access token URL ( obtained from the portal “Endpoints” ā€“ see next image ), the client id from the app registration and of course, the client_secret you configured on the app registration.

The Access Token URL can be obtained from the portal in the App Registrations blade by clicking on the “Endpoints” button:

Grab the OAuth 2.0 token endpoint (v2). You can also use your tenant name in place of the tenant id ( guid ):

Save those values in the postman screen and then click on “Request Token”

You should get an access token like so:

To use this token, scroll to the bottom and click on the “Use Token” button:

You can now make a graph call ( based on the permissions you setup in the app registration ā€“ groups in this example ) ā€“ the rest endpoint in this example is https://graph.microsoft.com/v1.0/groups :

If you copy the access token and paste it in http://jwt.ms you can decode the token to see the claims it has. If you notice, our token only has permissions for group.read.all ā€“ which shows up as a “role”. Application permissions always show up as roles. Notice, there is not a delegated permission in this token because delegated permissions, even if consented to, will not appear in the token for a client_credentials grant type.

You can also just do a raw post request to the authority endpoint with the same values like this, again, you need to add the client_id, client_secret, scope but you also need to add grant_type when doing it this way and for that value, be sure to use “client_credentials”:

The values go in the body of the request in this scenario.

Summary:

You can use postman to obtain an access token for testing your api calls, etc. using the client_credentials flow as demonstrated.

How to set your MSAL.js app up to automatically sign-in if you already have a session signed in on another tab

Our MSAL.js sample is an excellent example for using MSAL in a javascript page. However, it only demonstrates logging in by clicking on a button. If you’re launching your app from myapps portal or are otherwise already logged into the portal with the browser, it would make sense to just use that session to perform your sign-in for your MSAL app without requiring the button click.

This is actually easily done. Using the VanillaJSTestApp in the Samples folder of the repository, I made a simple code change to do this. First thing you need is to get an event listener setup for the Page load event. In the sample project, I did this by simply adding the following code in the Head:

<head>
    <title>Quickstart for MSAL JS</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.4/bluebird.min.js"></script>
    <script type="text/javascript" src="https://alcdn.msauth.net/lib/1.1.3/js/msal.js" integrity="sha384-m/3NDUcz4krpIIiHgpeO0O8uxSghb+lfBTngquAo2Zuy2fEF+YgFeP08PWFo5FiJ" crossorigin="anonymous"></script>
    <script type="text/javascript">
        if (typeof Msal === 'undefined') document.write(unescape("%3Cscript src='https://alcdn.msftauth.net/lib/1.1.3/js/msal.js' type='text/javascript' integrity='sha384-m/3NDUcz4krpIIiHgpeO0O8uxSghb+lfBTngquAo2Zuy2fEF+YgFeP08PWFo5FiJ' crossorigin='anonymous'%3E%3C/script%3E"));
        window.addEventListener("load", function () {
            console.log("onLoad");
            autoSignIn();
        });
    </script>
</head>

Next, I created my own method called “autoSignIn()” and for the most part, I just copied the “signIn” method and tweaked it a bit. First, I didn’t want the popup to occur which, when using the loginPopup method would flash a window. Instead, I chose to do the loginRedirect method. That would not show the popup unless you are not actualy signed in. However, as part of that flow, after successfully signing in, you would be redirected to the path that you have configured to redirect and so you must have your redirect handler in place, which the sample does and it is called “authRedirectCallBack”.

So, my sample code for the autoSignIn method looks like this:

function autoSignIn() {
    if (myMSALObj.getAccount() == null) {
        myMSALObj.loginRedirect(loginRequest).then(function (loginResponse) {
            showWelcomeMessage();
            myMSALObj.acquireTokenSilent(tokenRequest).then(function (tokenResponse) {
              callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);
            });
        });
    }
}

And that is it. This would cause the sample project to automatically sign-in if you’re already authenticated to Azure in another session.

Infinite sign in loop between MVC application and Azure AD

In a previous post I talked about an infinite redirect loop issue between an MVC application and Azure AD when performing sign in. In this post, we will look at another scenario which can lead to the same type of problem.

Background

Applications running an old version of OWIN middleware can run into this issue because of a known Katana bug. Due to a cookie mismanagement issue in the old version of OWIN, an asp.net application is not able to recognize an authenticated request from Azure AD so it keeps sending the request back to Azure AD for login leading to the infinite loop issue.

Error Message

Customer may see this error message in the browser:  “We couldn’t sign you in.  Please try again.”

How to recognize the Katana bugā€¦

Capture a Fiddler trace and examine one of the later redirect frames back to the web application. Note in the screen shot below, the request in frame 58 contains multiple OpenIdConnect nonce cookies (red-circled). In a working scenario, we should only have one OpenIdConnect nonce cookie set at the beginning before authentication. After the request is successfully authenticated, this nonce cookie is destroyed and asp.net sets its own session cookie. Because of this bug, we see there is a build up of these nonce cookies.

Resolving the issueā€¦

The problem has been fixed in ASP.NET core and in the new version of Katana Owin for ASP.NET.  To resolve this issue, you can upgrade your application to use ASP.NET Core. If you must continue stay on ASP.NET, perform the following:

  1. Update your application’s Microsoft.Owin.Host.SystemWeb package be at least version 3.1.0.0 and
  2. Modify your code to use one of the new cookie manager classes, for example something like the following:
app.UseCookieAuthentication(new CookieAuthenticationOptions 
{ 
    AuthenticationType = "Cookies", 
    CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager() 
});

or

app.UseCookieAuthentication(new CookieAuthenticationOptions() 
{ 
    CookieManager = new SystemWebCookieManager() 
});

Note: In some cases where the application is hosted under a virtual directory or Application instead of the root of the web site, the above solution may not work. See https://stackoverflow.com/questions/44397715/infinite-re-direct-loop-after-aad-authentication-when-redirect-is-specified and https://github.com/aspnet/AspNetKatana/issues/203 for more info. For instance, suppose you have the following environment

Root of web site: https://mysite – this runs under Application Pool 1

Application test2 under the root root: https://mysite/test2 – this runs under Application Pool 2

Your Asp.NET application is runs under https://mysite/tes2 Application with the following code:

        public void Configuration(IAppBuilder app)
        {
            // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
            app.UseCookieAuthentication(new CookieAuthenticationOptions());
            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    // Sets the ClientId, authority, RedirectUri as obtained from web.config
                    ClientId = clientId,
                    Authority = authority,
                    RedirectUri = "https://mysite/test2",
                    // PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
                    PostLogoutRedirectUri = redirectUri,
                    Scope = OpenIdConnectScope.OpenIdProfile,
                    // ResponseType is set to request the id_token - which contains basic information about the signed-in user
                    ResponseType = OpenIdConnectResponseType.IdToken,
                    // ValidateIssuer set to false to allow personal and work accounts from any organization to sign in to your application
                    // To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name
                    // To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
                    
                   
                    // OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
                    
                    Notifications = new OpenIdConnectAuthenticationNotifications
                    {
                        AuthenticationFailed = OnAuthenticationFailed
                    }
                    
                    
                }
            );
        }

And you are using the following code for trigger the Sign In flow:

        public void SignIn()
        {
            if (!Request.IsAuthenticated)
            {
                HttpContext.GetOwinContext().Authentication.Challenge(
                    new AuthenticationProperties { RedirectUri = "/" },
                    OpenIdConnectAuthenticationDefaults.AuthenticationType);
            }
        }

The above scenario can result in an authentication infinite loop with a build-up of multiple OpenIdConnect.nonce cookies as well. The difference here is that Asp.NET does not appear to set its authenticated session cookies. This particular problem can be resolved using the following code change to set the redirect URLs in both the OpenID Connect initialization code and the Challenge method (note the trailing slash in the redirect URL):

app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    // Sets the ClientId, authority, RedirectUri as obtained from web.config
                    ClientId = clientId,
                    Authority = authority,
                    RedirectUri = "https://mysite/test2/",
                    // PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
                    PostLogoutRedirectUri = redirectUri,
                    Scope = OpenIdConnectScope.OpenIdProfile,
...
        public void SignIn()
        {
            if (!Request.IsAuthenticated)
            {
                HttpContext.GetOwinContext().Authentication.Challenge(
                    new AuthenticationProperties { RedirectUri = "/test2/" },
                    OpenIdConnectAuthenticationDefaults.AuthenticationType);
            }
        }

 

References

https://varnerin.info/infinite-redirects-with-aspnet-owin-and-openid-connect/

Updated 11/16/2020 – add info regarding virtual Directory / Application

How to acquire bearer token non-interactively with a federated user

You are looking for a way to acquire an access token from Azure Active Directory without user interaction. We highly recommended to always use an interactive user sign-in experience as this is the most secured method. Especially when your organization has conditional access policies which require Multi-Factor Authentication. If at all possible, please use the methods for interactive sign-in.

Here are some general guidance on how to non-interactively acquire a access token.


Client Credentials grant flow (Recommended)
https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-client-creds-grant-flow

Note: There are some services that does not support the client access token issued when using Client Credentials.


Resource Owner Password Credential grant flow (Not recommended)
https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc

Note Only use this if Client Credentials is not supported for the resource and no other flows are an option. Also only use if the application is behind a protected and secured environment


On-behalf-of flow
https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-on-behalf-of-flow


When trying to use ADAL/MSAL

It will attempt to pull the federation services metadata to get the active endpoint (i.e. if your using AD FS, this is the usernamemixed endpoint) and will send the user name and password to the active endpoint. If you get errors, you will need to troubleshoot the federation service. Active endpoint must be publicly available and token issuance policy rules must allow the call to the usernamemixed endpoint. (Hint: Generally when you have legacy authentication configured to be blocked, this will also impact the call to the usernamemixed endpoint.)

Note: If you can’t get this resolved from the federation service, then consider using a cloud only user account or do not use ADAL/MSAL at all.

I would suggest making your application registration a Native App or aka Public Client. This is because ADAL/MSAL does not have any methods for Resource Owner Password Credential flow if the application registration is a Web App. Generally with web applications, it is expected that you will be using one of the other flows I.e. Authorization Code or On-behalf-of.

There are two ways to ensure the authentication is using the application as the Native Client (Public Client) context…

Method One: Use a Public Client redirect URI…


Method Two: Enable Default client type as public client…

Example…

For example when using ADAL.NET, the code will look something like this…

//...
UserPasswordCredential user;
user = new UserPasswordCredential(userName, password);
result = authContext.AcquireTokenAsync(resourceId, clientId, user).Result; 
//...

When you are calling the token endpoint directlyā€¦

So you are attempting to perform Resource Owner Password Credential flow directly based on the following guidance…

https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc

You might get the following similar error message…

AADSTS50126:Ā ErrorĀ validatingĀ credentialsĀ dueĀ toĀ invalidĀ usernameĀ orĀ password.

Ensure Direct Authentication is enabled…

To do this…

  1. Make sure AzureAdPreview PowerShell module is installed
    https://www.powershellgallery.com/packages/AzureADPreview/2.0.2.129
  2. Run the following PowerShell Command to create a new Azure AD Policy for Direct Authentication…
    $policyid = New-AzureADPolicy -Definition @("{"HomeRealmDiscoveryPolicy":{"AllowCloudPasswordValidation":true}}") -DisplayName EnableDirectAuthPolicy -Type HomeRealmDiscoveryPolicy
  3. Then apply the policy to the ServicePrincipal object (Enterprise App)
    Add-AzureADServicePrincipalPolicy -Id <ObjectID of the Service Principal> -RefObjectId $policyid

For more information see…

https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/configure-authentication-for-federated-users-portal#enable-direct-authentication-for-legacy-applications

And ensure Password sync is enabledā€¦
https://docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-password-hash-synchronization

If you’re not able to enable Direct Authentication or enable Password sync, then you will not be able to use a Federated user account. In that case, you will need to use a non-federated user account. This can be done by creating a new user account in the Azure AD portal using any of the other non-federated domains.

Temporary passwords and password resets are not synchronized to Azure AD.


Keep these in mind…

Keep in mind you might have conditional access policies that may block you from getting a token non-interactively. If that’s the case, discuss with your security team. If you get approval to allow this non-interactive token acquisition, then add the user as an exception to the conditional access policy.