2
votes

I am trying to authenticate multiple clients (connecting from a windows server application - no browser) access to my .Net4.51 webAPI using azure active directory.

I have created an Azure AD application for my webapi called "ServerWebApi" and configured my webapi application code to point to it in the webConfig using the following app settings.

<add key="ida:Tenant" value="myDomainName.onmicrosoft.com" />
<add key="ida:Audience" value="https://myDomainName.onmicrosoft.com/ServerWebApi" />
<add key="ida:ClientID" value="<client id>" />

Thus when a request comes in with a token, it should use these settings to validate the token against my AD WebApi application called ServerWebApi.

Now to give each client the ability to get a token, I have created an Azure application for each client. (I didnt create it as a native app but instead as a webapi app so that I can create a separate key for each client). I also added my actual webAPI application (ServerWebApi) in Azure into the list of "Web APIs accessed by this application". (For this example my first client is called myClientWebApiApp)

So now my client can successfully request a token from its client App in AD but when it posts to the server (webapi) I get a 401 - Unauthorized. Thus I presume I am failing validation of the token against my Azure AD ServerWebApi webApi.

The client app code I use to get the token and call my webApi is as follows

// Create an ADAL AuthenticationContext object and link it to my tennant domain myDomainName.onmicrosoft.com
var authority = ConfigurationManager.AppSettings["Tenant"];
authenticationContext = new AuthenticationContext(authority);

//Client Cerdential Object used while Acquiring a token from Windows Azure AD
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);

// Invoke AuthenticationContext.AcquireToken to obtain an AccessToken. 
// Uses previously-created ClientCredential to authenticate
authenticationResult = authenticationContext.AcquireToken(resource, clientCred);

httpClient.DefaultRequestHeaders.Clear();
//Add authorization header to HttpClient
httpClient.DefaultRequestHeaders.Authorization = new        AuthenticationHeaderValue("Bearer",authenticationResult.AccessToken);

var data = "new Value";

// Call the Service web api to update                    
HttpResponseMessage response = httpClient.PostAsJsonAsync(resourceurl + "api/Values", data).Result;

And it uses the following config settings

<add key="Tenant" value="https://login.windows.net/myDomainName.onmicrosoft.com"/>
<add key="ClientId" value= "<client id>"/>
<add key="ClientSecret" value= "<client secret>"/>
<add key="ResourceUrl" value="https://localhost:44300/"/>
<add key="AppIdUri" value="https://myDomainName.onmicrosoft.com/myClientWebApiApp"/>

The Client Id is that of the Client webapi application I created and the ClientSecret is a key I created against that application in Azure AD.

Does anyone know if what I am doing is possible and if so what am I doing wrong?

2
please make sure you don't post real client API keys! The internet is a scary place... - Kevin Brown

2 Answers

1
votes

I believe your problem is ultimately in how you are setting up the AuthorizationHeader and possibly how your ServerWebApi is responding to the request.

Take a look at this blog. I believe this is the solution you are looking for.

0
votes

I am also facing the similar issue and waiting for the solution. But as a work around I have created as below. I am sure it might not be an appropriate fix. Better recommendations for the answer would surely helps us.

Step1: Added the below line to Register method in WebApiConfig.cs

config.MessageHandlers.Add(new AzureADAuthorizationHandler
{
    Audience = ConfigurationManager.AppSettings["Audience"],
    SymmetricKey = ConfigurationManager.AppSettings["ClientSecretKey"]
});

Step2: ( Note below implementation is Work In Progress as mentioned in the comments)

using System;
using System.IdentityModel.Tokens;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;

namespace MyDevWebApiService.Common
{
    public class AzureADAuthorizationHandler : DelegatingHandler //: AuthorizeAttribute
    {
         //public override void OnAuthorization(HttpActionContext actionContext)
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage httpRequestObject, CancellationToken cancellationToken)
        {
            HttpResponseMessage httpResponseMessage = null;

            try
            {
                var RequuestObj = System.Web.HttpContext.Current.Request;
                var AuthHeader = RequuestObj.Headers["Authorization"];

                if (AuthHeader == null)
                {
                    httpResponseMessage = httpRequestObject.CreateErrorResponse(HttpStatusCode.BadRequest, "No authorization header present in the request");
                }
                else
                {
                    string encodedTokenString = AuthHeader.StartsWith("Bearer ") ? AuthHeader.Substring(7) : AuthHeader;
                    //Validate date the token - Work in Progress. Should be able to handle Bearer and Basic headers.
                    //Extract the Azure Ad info (tenant, client id and client security key etc) and use ADAL to authenticate with Azure AD.
                    SetCurrentPrincipal(encodedTokenString);
                }
            }            
            catch (Exception ex)
            {
                httpResponseMessage = httpRequestObject.CreateErrorResponse(HttpStatusCode.BadRequest, ex + "Invalid header, header token extration failed");
            }

            return httpResponseMessage != null ? Task.FromResult(httpResponseMessage) : base.SendAsync(httpRequestObject, cancellationToken);
        }

        public void SetCurrentPrincipal(string encodedTokenString)
        {
            try
            {
                ClaimsIdentity ClaimsIdentityObject = new ClaimsIdentity();

                JwtSecurityToken JwtSecurityTokenObject = new System.IdentityModel.Tokens.JwtSecurityToken(encodedTokenString);

                foreach (Claim ClaimObj in JwtSecurityTokenObject.Claims)
                {
                    ClaimsIdentityObject.AddClaim(ClaimObj);
                }

                Thread.CurrentPrincipal = new ClaimsPrincipal(ClaimsIdentityObject);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
}

=================================================================================================== Note: Passive Federation:

When we create MVC Project in VS2013 and .NET4.5.1 it will create a persistence local db in the project and store all authentication details.

Not comfortable tweaking this passive federation for active federation especially persistent storage point of view.

Below classes will be generated automatically.

  1. Models.TenantDbContext
  2. Models.IssuingAuthorityKey
  3. Models.Tenant
  4. Utils.DatabaseIssuerNameRegistry
  5. IdentityConfig
  6. Additional line -> IdentityConfig.ConfigureIdentity(); In Global.asax.cs Application_Start()

  7. Config file entries.