MsalServiceException: AADSTS90023: Public clients can't send a client secret

1.8k Views Asked by At

I've created an issue on github in case this is a bug.

PROBLEM

I'm trying to log in to an ASP.Net Core 2 Web App, using Azure AD with MSAL.

When I call AcquireTokenByAuthorizationCodeAsync I get an error message.

MsalServiceException: AADSTS90023: Public clients can't send a client secret..

There's not many examples of coding doing exactly what I'm doing, but what does exist shows the ClientSecret being passed to the ConfidentialClientApplication as a ClientCredential.

It's doubly confusing because the error message refers to a PublicClientApplication which is not what I'm using, I'm using ConfidentialClientApplication.

CODE

Here's the full method where the error occurs:

public void Configure(string name, OpenIdConnectOptions options)
{
    options.Authority = $"{azureADOptions.Instance}{azureADOptions.TenantId}";
    options.CallbackPath = azureADOptions.CallbackPath;
    options.ClientId = azureADOptions.ClientId;
    options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
    options.UseTokenLifetime = true;

    options.Events = new OpenIdConnectEvents
    {
        OnAuthorizationCodeReceived = async context =>
        {
            var clientID = options.ClientId;
            var authority = options.Authority;
            var redirectUri = this.azureADOptions.RedirectUri;
            var clientCredentials = new ClientCredential(azureADOptions.ClientSecret);
            var tokenCache = AzureADUtils.GetTokenCache(context.HttpContext, context.Principal);

            var clientApp = new ConfidentialClientApplication(clientID, authority, redirectUri, clientCredentials, tokenCache, null);

            try
            {
                var code = context.ProtocolMessage.Code;
                var scopes = new[]
                {
                    AzureADScopes.User.ReadBasicAll,
                    AzureADScopes.Group.ReadAll
                };

                //AJ: This line is where the exception is thrown.
                var result = await clientApp.AcquireTokenByAuthorizationCodeAsync(code, scopes);

                context.HandleCodeRedemption(result.AccessToken, result.IdToken);
            }
            catch (Exception)
            {
                throw;
            }
        }
    };
}

The error message appears to come from the server-side, rather than being generated client-side, which makes a bit more sense as to why it's getting confused as to the type of *ClientApplication class I'm using.

REQUEST & RESPONSE

Here's the request I send:

POST https://login.microsoftonline.com/[REMOVED]/oauth2/v2.0/token HTTP/1.1
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
Accept: application/json
Cookie: x-ms-gateway-slice=006; stsservicecookie=ests; esctx=[REMOVED]
x-client-SKU: MSAL.CoreCLR
x-client-Ver: 1.1.0.0
x-client-OS: Microsoft Windows 10.0.15063 
client-request-id: [REMOVED]
return-client-request-id: true
x-ms-request-root-id: [REMOVED]
x-ms-request-id: [REMOVED]
Request-Id: [REMOVED]
Content-Length: 981
Host: login.microsoftonline.com

client_id=[REMOVED]
&client_info=1
&client_secret=[REMOVED]
&scope=Group.Read.All+offline_access+openid+profile+User.ReadBasic.All
&grant_type=authorization_code
&code=[REMOVED]
&redirect_uri=https%3A%2F%2Flocalhost%3A44365%2Fsignin-oidc

And the response I get back:

{
    "error": "invalid_request",
    "error_description": "AADSTS90023: Public clients can't send a client secret.\r\nTrace ID: [REMOVED]\r\nCorrelation ID: [REMOVED]\r\nTimestamp: 2017-09-18 16:12:51Z",
    "error_codes": [90023],
    "timestamp": "2017-09-18 16:12:51Z",
    "trace_id": "[REMOVED]",
    "correlation_id": "[REMOVED]"
}

This is very close to what is described in this MSDN Blog post under the heading "Step 2 - Get Access Token".

APP REGISTRATION

I've created a "Converged" application registration, which should be using the Azure AD v2 app model.

It has a single Application Secret, a Password, which is used as the Client Secret.

It has a single Platform, Web, with Allow Implicit Flow enabled, a single Redirect URL set, and no Logout URL.

2

There are 2 best solutions below

0
On

I know the post is quiet old, but I just have had exactly the same problem. I found a resolution thanks to the documentation found here : https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Client-credential-flows.

My code works now with :

var app = ConfidentialClientApplicationBuilder.Create(options.ClientId)
            .WithClientSecret(options.ClientSecret)
            .WithRedirectUri("https://localhost:7001/signin-oidc")
            .Build();

var result = await app.AcquireTokenByAuthorizationCode(options.Scope, context.ProtocolMessage.Code)
            .WithAuthority(AzureCloudInstance.AzurePublic, options.TenantId)
            .ExecuteAsync();

Be very careful to have a scope for which the user has consent and DO NOT USE common or organization as tenant id in the WithAuthority method parameter but your tenant id (the guid one)

Hope it's help !

0
On

This example shows how to make this work: https://azure.microsoft.com/en-gb/resources/samples/active-directory-dotnet-webapp-openidconnect-aspnetcore/

I have used it successfully.