question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Client credentials flow fails with 401 Not Authorized from the resource server

See original GitHub issue

Confirm you’ve already contributed to this project or that you sponsor it

  • I confirm I’m a sponsor or a contributor

Version

3.x

Describe the bug

I have a separate authorization server and a resource server. The resource server runs in IIS and have Windows Integrated Security activated too. This means that the resource server should work for both NTLM and bearer tokens (client credentials and authorization code flow).

I am successfully connecting to the auth server, getting an access token and passing it along to the resource server.

However the request is failing with 401 Unauthorized. The request only contains an Authorization header and there is no Negotiate header (as it should be) as this request must be done with the bearer token and not with network credentials.

To reproduce

The authorization server is setup as follows:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
        {
            options.UseSqlServer(Configuration.GetConnectionString("Identity"));
            options.UseOpenIddict();
        });

        services.Configure<IdentityOptions>(options =>
        {
            options.ClaimsIdentity.UserNameClaimType = Claims.Name;
            options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
            options.ClaimsIdentity.RoleClaimType = Claims.Role;
        });

        services.AddQuartz(options =>
        {
            options.UseMicrosoftDependencyInjectionJobFactory();
            options.UseSimpleTypeLoader();
            options.UseInMemoryStore();
        });

        services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);

        services.AddOpenIddict()
            .AddCore(options =>
            {
                options.UseEntityFrameworkCore().UseDbContext<ApplicationDbContext>();
                options.UseQuartz();
            })

            .AddServer(options =>
            {
                options.SetAuthorizationEndpointUris("/connect/authorize")
                        .SetLogoutEndpointUris("/connect/logout")
                        .SetIntrospectionEndpointUris("/connect/introspect")
                        .SetTokenEndpointUris("/connect/token")
                        .SetUserinfoEndpointUris("/connect/userinfo")
                        .SetVerificationEndpointUris("/connect/verify");

                options.AllowAuthorizationCodeFlow() //.RequireProofKeyForCodeExchange()
                        .AllowPasswordFlow()
                        .AllowClientCredentialsFlow()
                        .AllowImplicitFlow()
                        .AllowHybridFlow()
                        .AllowRefreshTokenFlow();

                options.RegisterScopes(
                    Scopes.OpenId, 
                    Scopes.Email, 
                    Scopes.Profile, 
                    Scopes.Roles,
                    Scopes.OfflineAccess);

                options.AddEncryptionKey(new SymmetricSecurityKey(Convert.FromBase64String(Configuration["Identity:EncryptionKey"])));

                options.AddDevelopmentSigningCertificate();

                options.UseAspNetCore()
                        .EnableStatusCodePagesIntegration()
                        .EnableAuthorizationEndpointPassthrough()
                        .EnableLogoutEndpointPassthrough()
                        .EnableTokenEndpointPassthrough()
                        .EnableUserinfoEndpointPassthrough()
                        .EnableVerificationEndpointPassthrough();

                options.DisableAccessTokenEncryption();
            })

            .AddValidation(options =>
            {
                options.UseLocalServer();
                options.UseAspNetCore();
            });
        });
    }

The resource server is configured as follows:

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
    });

    services.AddOpenIddict()
        .AddValidation(options =>
        {
            options.SetIssuer(Configuration["Issuer"]);
            options.AddAudiences(Configuration["Audiences"]);

                options.UseIntrospection()
                    .SetClientId(Configuration["ClientId"])
                    .SetClientSecret(Configuration["ClientSecret"]);

            options.UseSystemNetHttp();
            options.UseAspNetCore();
        });

I’m using Flurl to get an access token and then connect to a resource endpoint protected with the [Authorize] attribute. The code is:


    //Get access tokens
    var tokens = await authServerUrl
        .AppendPathSegment("connect/token")
        .WithHeader("content-type", "application/x-www-form-urlencoded")
        .PostUrlEncodedAsync(new {
            grant_type = "client_credentials",
            client_id = clientId,
            client_secret = clientSecret,
            audience = audience,
        })
        .ReceiveJson<ClientCredentialsResponse>();

    //Access protected endpoint
    var response= await serverUrl
        .AppendPathSegment("api/Protected/Endpoint")
        .WithOAuthBearerToken(tokens.AccessToken)
        .ReceiveString();

The ClientCredentialsResponse class is a simple POCO containing access_token, token_type and expires_in.

Exceptions (if any)

No response

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:10 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
cryo75commented, May 19, 2022

Oh wow I was using audience instead from the Auth0 specification. I do get the audience in the token now!!

0reactions
kevinchaletcommented, May 18, 2022

Then don’t forget to request it:

var tokens = await authServerUrl
    .AppendPathSegment("connect/token")
    .WithHeader("content-type", "application/x-www-form-urlencoded")
    .PostUrlEncodedAsync(new {
        grant_type = "client_credentials",
        client_id = clientId,
        client_secret = clientSecret,
        // 👇
        scope = "your_custom_scope"
    })
    .ReceiveJson<ClientCredentialsResponse>();
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to Quickly Fix the 401 Unauthorized Error (5 Methods)
The 401 error is commonly associated with invalid authentication credentials. Find out more about the causes and fixes.
Read more >
How to Resolve the 401 Unauthorized Error when ...
The HTTP status code 401 Unauthorized indicates that the request lacks valid authentication credentials for the target resource. In simpler ...
Read more >
401 Unauthorized when accessing /messages api using ...
401 Unauthorized when accessing /messages api using client credentials grant flow.
Read more >
OAuth2 401 Unauthorized from resource server
The authentication part (sending credentials to the authorization server and getting back an access_token) works fine. I get a valid JWT token.
Read more >
401/Unauthorized when obtaining token in Authorization ...
When using “Authorization Code” flow within a SPA using Auth0 Quickstart example ... Client Credentials is grayed out, and not checked.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found