Mike
Mike

Reputation: 155

Validate Oauth Bearer token, IIssuerSecurityTokenProvider missing in Microsoft.Owin.Security.Jwt 4.x

I'm trying to get my .Net Web API (Azure API App using OWIN) to accept an OAuth Bearer token for the client_credentials grant, but I keep getting 401 Unauthorized.

It also seems all the Microsoft samples are outdated (not conforming to the latest nuget packages for Owin).

JwtFormat exects an IIssuerSecurityTokenProvider, but that does not exist anymore - instead the JwtFormat expects an IIssuerSecurityKeyProvider, but I can't wrap my head around how to use it.

The Azure OAuth Server is working

I have registered two applications (the api and the client) in Azure Active Directory. Since this is just quick demo, I'll give you all the id's and secrets ;)

I can get a token from Azure AD, see https://reqbin.com/817shtc2 for a complete request, so far so good.

API

ClientId: 44cf7574-88a2-42d6-9497-bff43cc8dc09

Endpoint: https://apim-demo-mglentoft.azure-api.net/api/Values (GET)

Client

ClientId: 5f7ee334-b8db-46d3-972f-09f52e186d1d

Secret: ggKp94]HZHWZ.c*5wUC?ToSVfknyqLB3

I followed the sample at https://github.com/azureadquickstarts/appmodelv2-nativeclient-dotnet, but it references Microsoft.Owin.Security.Jwt.IIssuerSecurityTokenProvider which does not exist in nuget v4.0.1.0.

I tried just commenting out the second parameter for JwtFormat, but that does not work. Any ideas how to get this working using the Microsoft.Owin.Security.Jwt.IIssuerSecurityKeyProvider?

Below is the entire startup.cs

I can get this working using .Net Core, but for various reasons I have to stick with .Net Framework 4.7.2

using System;
using System.Configuration;
using System.Threading.Tasks;
using System.Web.Http;
using DemoAPI.Middleware;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Cors;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Jwt;
using Microsoft.Owin.Security.OAuth;
using Owin;

[assembly: OwinStartup(typeof(DemoAPI.App_Start.Startup))]

namespace DemoAPI.App_Start
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var config = new HttpConfiguration();

            ConfigureAuth(app);
            app.Use(typeof(CorrelationHandlerMiddleware));


            app.UseCors(CorsOptions.AllowAll);
            WebApiConfig.Register(config);
            app.UseWebApi(config);
        }

        private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];

        public void ConfigureAuth(IAppBuilder app)
        {
            // NOTE: The usual WindowsAzureActiveDirectoryBearerAuthentication middleware uses a
            // metadata endpoint which is not supported by the v2.0 endpoint.  Instead, this 
            // OpenIdConnectSecurityTokenProvider implementation can be used to fetch & use the OpenIdConnect
            // metadata document - which for the v2 endpoint is https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration


            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
            {
                AccessTokenFormat = new JwtFormat(
                    new TokenValidationParameters
                    {
                        // Check if the audience is intended to be this application
                        ValidAudiences = new[] { clientId, $"api://{clientId}" },`enter code here`

                        // Change below to 'true' if you want this Web API to accept tokens issued to one Azure AD tenant only (single-tenant)
                        // Note that this is a simplification for the quickstart here. You should validate the issuer. For details, 
                        // see https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore
                        ValidateIssuer = false,

                    }//,
                     //new OpenIdConnectSecurityKeyProvider("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")
                     //the OpenIdConnectSecurityKeyProvider implements IIssuerSecurityTokenProvider, which is not part of Microsoft.Owin.Security.Jwt 4.0
                ),
            });
        }
    }
}

The actual error when using Microsoft.Owin.diagnostics

Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationMiddleware Error: 0 : Authentication failed Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10501: Signature validation failed. Unable to match key: kid: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. Exceptions caught: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. token: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)

Upvotes: 1

Views: 5904

Answers (2)

Mike
Mike

Reputation: 155

I'll go ahead and answer my own question here since I got it to work by slightly changing the sample in https://github.com/azureadquickstarts/appmodelv2-nativeclient-dotnet.

It could be that Stef's answer is working as well and I just did something wrong - but you sent me in the right direction :)

in Startup.cs:

I changed from an implementation of IIssuerSecurityTokenProvider to IIssuerSecurityKeyProvider (thanks Stef)

string issuerEndpoint = @"https://sts.windows.net/919e9a01-27bf-4106-9d36-48528249d0ce/";
            string metadaEndpoint = $"{issuerEndpoint}v2.0/.well-known/openid-configuration";

            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
            {
                AccessTokenFormat = new JwtFormat(
                    new TokenValidationParameters
                    {
                        // Check if the audience is intended to be this application
                        ValidAudiences = new[] { clientId, $"api://{clientId}" },

                        // Change below to 'true' if you want this Web API to accept tokens issued to one Azure AD tenant only (single-tenant)
                        // Note that this is a simplification for the quickstart here. You should validate the issuer. For details, 
                        // see https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore
                        ValidateIssuer = false,

                    },
                     new OpenIdConnectSecurityKeyProvider("https://sts.windows.net/919e9a01-27bf-4106-9d36-48528249d0ce/v2.0/.well-known/openid-configuration")
                ),
            });

The OpenIDConnectSecurityKeyProvider.cs

Implementation based on sample from https://github.com/azureadquickstarts/appmodelv2-nativeclient-dotnet, changed it to retrieve the keys instead of tokens.

using System.Collections.Generic;
using System.Threading;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security.Jwt;

namespace DemoAPI.App_Start
{
    //This class is necessary because the OAuthBearer Middleware does not leverage
    // the OpenID Connect metadata endpoint exposed by the STS by default.

    public class OpenIdConnectSecurityKeyProvider : IIssuerSecurityKeyProvider
    {
        public ConfigurationManager<OpenIdConnectConfiguration> ConfigManager;
        private string _issuer;
        private IEnumerable<SecurityKey> _keys;
        private readonly string _metadataEndpoint;

        private readonly ReaderWriterLockSlim _synclock = new ReaderWriterLockSlim();

        public OpenIdConnectSecurityKeyProvider(string metadataEndpoint)
        {
            _metadataEndpoint = metadataEndpoint;
            ConfigManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint, new OpenIdConnectConfigurationRetriever());

            RetrieveMetadata();
        }

        /// <summary>
        /// Gets the issuer the credentials are for.
        /// </summary>
        /// <value>
        /// The issuer the credentials are for.
        /// </value>
        public string Issuer
        {
            get
            {
                RetrieveMetadata();
                _synclock.EnterReadLock();
                try
                {
                    return _issuer;
                }
                finally
                {
                    _synclock.ExitReadLock();
                }
            }
        }

        public IEnumerable<SecurityKey> SecurityKeys
        {
            get
            {
                RetrieveMetadata();
                _synclock.EnterReadLock();
                try
                {
                    return _keys;
                }
                finally
                {
                    _synclock.ExitReadLock();
                }
            }
        }

        private void RetrieveMetadata()
        {
            _synclock.EnterWriteLock();
            try
            {
                OpenIdConnectConfiguration config = ConfigManager.GetConfigurationAsync().Result;
                _issuer = config.Issuer;
                _keys = config.SigningKeys;
            }
            finally
            {
                _synclock.ExitWriteLock();
            }
        }
    }
}

Upvotes: 4

Stef
Stef

Reputation: 81

try renaming 'token' for 'key' and you might be good.

So, instead of

    IssuerSecurityTokenProviders = new IIssuedSecurityTokenProvider[]
                                   {
                                       new symmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
                                   }

You should have something like

    IssuerSecurityKeyProviders= new IIssuerSecurityKeyProvider[]
                                   {
                                       new SymmetricKeyIssuerSecurityKeyProvider(issuer, audienceSecret)
                                   }

For more info: Issue Thread or Actual Github Repo

Hope this will help you...

Upvotes: 8

Related Questions