AzureAD / microsoft-authentication-library-for-dotnet

Microsoft Authentication Library (MSAL) for .NET
https://aka.ms/msal-net
MIT License
1.39k stars 340 forks source link

Trouble finding examples for 1.0 tokens and .net framework 4.7.2 #1886

Closed negberts closed 4 years ago

negberts commented 4 years ago

Hi,

We have an Service Fabric application that is now using ADAL and an Angular SPA that call's the API of the service fabric application. The API runs on .net framework 4.7.2. We want to migrate to MSAL.

I have succesfully configured Angular to attach bearer tokens to the API. However, in the API the user is still unauthorized.

The scope of the token is http://ilionx.onmicrosoft.com/PimLocalSfWebApi/user_impersonation

Migrating to version 2.0 scopes is not an option for now, since we have a lot of customers that would have to update their app registrations in their own tenants.

The Web API is configured like this in startup.cs:

`ITenantConfigurationRepository tenantRepository = kernel.Get(); List allTenants = tenantRepository.GetAllAsync().Result; var validAudiences = allTenants.Select(tenant => tenant.WebApiAppUri).ToList(); var validIssuers = allTenants.Select(tenant => string.Format("https://sts.windows.net/{0:D}/", tenant.AzureAdTenantId)).ToList();

        var tvps = new TokenValidationParameters
        {
            ValidAudiences = validAudiences,
            ValidIssuers = validIssuers,
            IssuerValidator = IssuerValidator.Validate
        };

        appBuilder.UseOAuthBearerAuthentication(new Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationOptions
        {
            AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectSecurityTokenProvider("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"))
        });`

The OpenIdConnectSecurityTokenProvider has the following implementation:

`namespace Pim.ServiceFabricApi { using System.Collections.Generic; using System.Linq; using System.Threading; using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.Owin.Security.Jwt;

/// <summary>
/// The OpenIdConnectSecurityTokenProvider class.
/// </summary>
/// <seealso cref="Microsoft.Owin.Security.Jwt.IIssuerSecurityKeyProvider" />
public class OpenIdConnectSecurityTokenProvider : IIssuerSecurityKeyProvider
{
    /// <summary>
    /// The synclock
    /// </summary>
    private readonly ReaderWriterLockSlim synclock = new ReaderWriterLockSlim();

    private readonly string metadataEndpoint;

    /// <summary>
    /// The configuration manager
    /// </summary>
    private ConfigurationManager<OpenIdConnectConfiguration> configManager;

    private string issuer;
    private IEnumerable<Microsoft.IdentityModel.Tokens.SecurityKey> tokens;

    /// <summary>
    /// Initializes a new instance of the <see cref="OpenIdConnectSecurityTokenProvider"/> class.
    /// </summary>
    /// <param name="metadataEndpoint">The metadata endpoint.</param>
    public OpenIdConnectSecurityTokenProvider(string metadataEndpoint)
    {
        this.metadataEndpoint = metadataEndpoint;
        this.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();
            }
        }
    }

    /// <summary>
    /// Gets all known security tokens.
    /// </summary>
    /// <value>
    /// All known security tokens.
    /// </value>
    public IEnumerable<Microsoft.IdentityModel.Tokens.SecurityKey> SecurityKeys
    {
        get
        {
            RetrieveMetadata();
            synclock.EnterReadLock();
            try
            {
                return tokens;
            }
            finally
            {
                synclock.ExitReadLock();
            }
        }
    }

    private void RetrieveMetadata()
    {
        synclock.EnterWriteLock();
        try
        {
            OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
            issuer = config.Issuer;
            tokens = config.SigningKeys.ToList();
        }
        finally
        {
            synclock.ExitWriteLock();
        }
    }
}

}`

All calls are receiving a 401 unauthorized error and ClaimsPrincipal.Current.Identity is always empty.

What am I missing? Is there any documentation for my scenario? (1.0 tokens and .NET framework) Help would be appriciated.

bgavrilMS commented 4 years ago

Hi @negberts

MSAL is only used to acquire tokens , it does not validate tokens. You might want to log a bug / question on https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet ?

negberts commented 4 years ago

@bgavrilMS thank you… I will give it a try there. Almost all examples are .net core and version 2.0 so it's hard to figure it out...

bgavrilMS commented 4 years ago

@jmprieur might also be able to help. It should be a fairly common scenario, as many endpoints need to accept both v1 and v2 access tokens.

I am not familiar with token validation, but just looking at your code:

jmprieur commented 4 years ago

@negberts The Azure AD endpoint (here v2.0 as you are using MSAL), is independent of the token version for the access token, which is entirely determined by what the Web API wants. See https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-registration#accepted-token-version So in your case MSAL will get you a v1.0 token, which will be validated by your API

Now it might be that one of the properties in your token is not the one expected by your Web API. What you might want to do is request the http://ilionx.onmicrosoft.com/PimLocalSfWebApi/.default scope (and do an admin consent of the scopes you need in the app registration).

negberts commented 4 years ago

@jmprieur I've added the default scope to the request. If I look at the response from acquireTokenSilent it looks good:

image

If I take the access token to http://jwt.ms it looks good as well:

{ "typ": "JWT", "alg": "RS256", "x5t": "SsZsBNhZcF3Q9S4trpQBTByNRRI", "kid": "SsZsBNhZcF3Q9S4trpQBTByNRRI" }.{ "aud": "http://ilionx.onmicrosoft.com/pimlocalsfwebapi", "iss": "https://sts.windows.net/4d75d01c-e20b-4b13-8e1d-f731f8ffbb3e/", "iat": 1592286847, "nbf": 1592286847, "exp": 1592290747, "acr": "1", "aio": "AVQAq/8PAAAAoPeFDo0JBRJ4e5extIdVzmxjgeiCAyBA9ChGYi6xfrHCsLMjdnNxrspbLnC5CNwXPC3Y7+GJXaW74PctZEXFY7LTaEfh2u7cHzoEclPuWk4=", "amr": [ "pwd", "mfa" ], "appid": "65c96f7d-3c6d-4808-96a8-b6bd0a70dbb5", "appidacr": "0", "family_name": "Egberts", "given_name": "Niels", "ipaddr": "", "name": "Niels Egberts", "oid": "59f777c7-332f-4f1b-8c9b-4f60d4fdbb27", "onprem_sid": "S-1-5-21-1292428093-261478967-1177238915-19470", "pwd_exp": "433015", "pwd_url": "https://adfs.ilionx.com/adfs/portal/updatepassword/", "scp": "user_impersonation", "sub": "kQzYdtn9_BuNiAmOwbyAm9REI5AJ8Aq6VVGogy8j3PE", "tid": "4d75d01c-e20b-4b13-8e1d-f731f8ffbb3e", "unique_name": "NEgberts@ilionx.com", "upn": "NEgberts@ilionx.com", "uti": "CxkamRQAo0mu3n_Z8fNvAA", "ver": "1.0" }.[Signature]

So what am I missing? I am still getting 401 unauthorized errors.

These are version 1.0 tokens, so I should not have to change my API, which is using ADAL and accepting 1.0 tokens, right?

If I compare the tokens from MSAL and ADAL they look the same…. Above is MSAL, below ADAL:

{ "typ": "JWT", "alg": "RS256", "x5t": "SsZsBNhZcF3Q9S4trpQBTByNRRI", "kid": "SsZsBNhZcF3Q9S4trpQBTByNRRI" }.{ "aud": "http://ilionx.onmicrosoft.com/PimLocalSfWebApi", "iss": "https://sts.windows.net/4d75d01c-e20b-4b13-8e1d-f731f8ffbb3e/", "iat": 1592290172, "nbf": 1592290172, "exp": 1592294072, "acr": "1", "aio": "AUQAu/8PAAAAEl5ADWy3QPTrf7NtPMMTfFm4PoK8w2DMPwN2S4MySlAM/YrtO5ZEcYo7usJ9l6i2yPsNdGKJgO59cY+WA5M0fw==", "amr": [ "pwd", "mfa" ], "appid": "65c96f7d-3c6d-4808-96a8-b6bd0a70dbb5", "appidacr": "0", "family_name": "Egberts", "given_name": "Niels", "ipaddr": "", "name": "Niels Egberts", "oid": "59f777c7-332f-4f1b-8c9b-4f60d4fdbb27", "onprem_sid": "S-1-5-21-1292428093-261478967-1177238915-19470", "pwd_exp": "429690", "pwd_url": "https://adfs.ilionx.com/adfs/portal/updatepassword/", "scp": "user_impersonation", "sub": "kQzYdtn9_BuNiAmOwbyAm9REI5AJ8Aq6VVGogy8j3PE", "tid": "4d75d01c-e20b-4b13-8e1d-f731f8ffbb3e", "unique_name": "NEgberts@ilionx.com", "upn": "NEgberts@ilionx.com", "uti": "_YZQMi2S4ECjWJP7m1_lAA", "ver": "1.0" }.[Signature]

negberts commented 4 years ago

To be clear: the ADAL token is accepted by the backend, the MSAL token is not... The backend has not changed because I reverted the MSAL changes there.

negberts commented 4 years ago

I figured it out... The audience is case sensitive…

jmprieur commented 4 years ago

Thanks for the update @negberts. Closing the issue.