GithubHelp home page GithubHelp logo

manuel-guilbault / aspnetcore.noninteractiveoidchandlers Goto Github PK

View Code? Open in Web Editor NEW
6.0 4.0 3.0 86 KB

A set of DelegatingHandlers for various HttpClient non-interactive authentication scenarios using Open ID Connect.

C# 98.16% PowerShell 1.84%
csharp oidc httpclient aspnetcore

aspnetcore.noninteractiveoidchandlers's Introduction

AspNetCore.NonInteractiveOidcHandlers

This library offers a set of DelegatingHandler subclasses for various HttpClient non-interactive authentication scenarii using Open ID Connect.

Some of the code in this library was directly copied from the IdentityModel.AspNetCore.OAuth2Introspection library.

Build Status Nuget version Nuget version (with prereleases) Nuget downloads

Table of Content

  1. Asp .NET Core
  2. Common options
    1. Authority
    2. ClientId
    3. ClientSecret
    4. AuthorityHttpClientAccessor
    5. DiscoveryPolicy
    6. TokenEndpoint
    7. Events
  3. Caching
    1. EnableCaching
    2. CacheKeyPrefix
    3. CacheDuration
    4. TokenExpirationDelay
  4. Supported non-interactive grant types
    1. client_credentials
    2. password
    3. refresh_token
    4. delegation
  5. Access token pass-through
  6. Token request failure

Asp .NET Core

This library exposes a bunch of extension methods on IHttpClientBuilder to add a DelegatingHandler to an HttpClient pipeline. The delegating handler will make sure to acquire an access token using the proper grant type and the provided information and inject the acquired token as Bearer in the HTTP request's Authorization header.

Example:

services
  .AddHttpClient("client-for-my-api")
  .AddOidcClientCredentials(options =>
  {
    options.Authority = "https://my-oidc-server";
    options.ClientId = "my-client-id";
    options.ClientSecret = "my-client-secret";
    options.Scope = "my-api-scope";
    options.EnableCaching = true;
    options.CacheDuration = TimeSpan.FromMinutes(10);
  })
;

In this example, the HttpClient named client-for-my-api will be injected a delegating handler which will acquire an access token using the client_credentials grant type when a first HTTP request is made. This access token will be cached for a maximum duration of 10 minutes, unless the token's expiration is shorter, in which case it will be cached until the token expires. Every subsequent request will reuse the cached token. A new token will be acquired every time a new request is sent and no token is fresh in cache.

Common options

Each type of handler expects its own set of options. However, all option types have the following common properties:

Authority

The URL of the OIDC server. Required.

ClientId

The client ID used to authenticate the token request. Required.

ClientSecret

The client's secret used to authenticate the token request. Required.

AuthorityHttpClientAccessor

A lambda returning an HttpClient. The lambda will be call before every request to the OIDC server, and the returned HttpClient instance will be used for the request. Default to a lambda which creates a new instance of HttpClient every time it is called.

DiscoveryPolicy

The DiscoveryPolicy used by the underlying IdentityModel API when requesting the discovery document. Default to the IdentityModel's default DiscoveryPolicy.

TokenEndpoint

The OAuth2 token endpoint. If set, the Authority property is ignored, and no discovery request happens, since the delegating handler will directly know where to request tokens.

Events

Token acquisition events. When the delegating handler tries to acquire a new access token, it will either call the OnTokenAcquired event or the OnTokenRequestFailed event, depending on the outcome.

For exemple, the OnTokenAcquired event can be useful to retrieve the new refresh token when using the refresh_token grant type and when the OIDC server renews the refresh token every time an access token is requested for it (e.g. when Identity Server's RefreshTokenUsage is set to OneTime).

Caching

In addition to the properties described above, all options have properties to control the caching of acquired access tokens:

EnableCaching

When set to true, the delegating handler will cache acquired tokens. An IDistributedCache service must be registered. Default to false.

CacheKeyPrefix

All cache keys will be prefixed with this value. Default to an empty string.

CacheDuration

The maximum duration for which an access token can be cached. The delegating handler will use the smallest value between this property and the token's expiration. Default to TimeSpan.MaxValue (which means the token's expiration is used by default).

TokenExpirationDelay

How much time before the token's expiration should the cache entry expire. Used when calculating the cache entry expiration to compare with the CacheDuration property. Default to 1 minute.

Supported non-interactive grant types

Below is a list of the supported non-interactive grant types and the extension method to use to register their delegating handler.

client_credentials

The client_credentials grant type is typically used for machine-to-machine, userless authentication. It produces an access token without any user information.

services
  .AddHttpClient("client-for-my-api")
  .AddOidcClientCredentials(options =>
  {
    options.Authority = "https://my-oidc-server";
    options.ClientId = "my-client-id";
    options.ClientSecret = "my-client-secret";
    options.Scope = "my-api-scope";
  })
;

password

The password grant type requires a username and a password, and produces an access token for the matching user. It is the least secure grant type and should be used only when nothing else can.

The option's UserCredentialsRetriever property must be set to a lambda which receives an IServiceProvider and returns a (string userName, string password)? nullable tuple. The delegating handler will call this lambda before trying to acquire a token. A null tuple can be returned, in which case the delegating handler won't request any token and won't authenticate the request.

services
  .AddHttpClient("client-for-my-api")
  .AddOidcPassword(options =>
  {
    options.Authority = "https://my-oidc-server";
    options.ClientId = "my-client-id";
    options.ClientSecret = "my-client-secret";
    options.Scope = "my-api-scope";
    options.UserCredentialsRetriever = (serviceProvider) => ("my-username", "my-password");
  })
;

refresh_token

The refresh_token grant type requires a refresh token that was previously produced for a specific user, scope and client, and produces an access token for its user.

The options's RefreshTokenRetriever property must be set to a lambda which receives an IServiceProvider and returns a refresh token as a string. The delegating handler will call this lambda before trying to acquire a token. A null value can be returned, in which case the delegating handler won't request any token and won't authenticate the request.

services
  .AddHttpClient("client-for-my-api")
  .AddOidcRefreshToken(options =>
  {
    options.Authority = "https://my-oidc-server";
    options.ClientId = "my-client-id";
    options.ClientSecret = "my-client-secret";
    options.RefreshTokenRetriever = (serviceProvider) => "my-refresh-token";
  })
;

delegation

The delegation grant type is a custom grant type which can be used to request a new access token for a different scope from an existing access token. It is typically used when an API (dubbed upstream) must call another API (dubbed downstream), and when both APIs require different scopes.

This use case is described in the Identity Server documentation.

In addition to registering the delegating handler, the AddOidcTokenDelegation extension method will make sure the IHttpContextAccessor service is registered, as it uses the current IHttpContext to look for an inbound Bearer token in the incoming request's Authorization header. If no inbound token is found, the delegating handler won't request any token and won't authenticate the outgoing request.

services
  .AddHttpClient("client-for-my-downstream-api")
  .AddOidcTokenDelegation(options =>
  {
    options.Authority = "https://my-oidc-server";
    options.ClientId = "my-upstream-api-client-id";
    options.ClientSecret = "my-upstream-api-client-secret";
    options.Scope = "my-downstream-api-scope";
  })
;

Access token pass-through

In addition to the aforementioned strategies, an ASP .NET Core application may simply need to pass its inbound access token through to any downstream API call. When applied in the context of an API calling another API, this is called poor man’s delegation (see Identity Server's doc ).

In the context of a web application using the authorization_code or the hybird grant type, such as an MVC application, it can make perfect sense to simply pass through the access token of the current authenticated user to downstream API calls, hence this extension method:

services
    .AddHttpClient("client-for-my-downstream-api")
    .AddAccessTokenPassThrough()
;

Here, the HttpClient instance named client-for-my-downstream-api will have in its pipeline a delegating handler which will try to retrieve a token named access_token from the request's authentication ticket and, if any, will inject its value as Bearer in the outbound HTTP request's Authorization header.

The name of the token (when retrieved from the authentication service) or where it is retrieved from can be override using the options' TokenRetriever property.

Token request failure

When a delegating handler fails to acquire an access token, it will log an error using ASP .NET Core's logging subsystem, and the HTTP request will be sent as unauthenticated. If you want to prevent sending unauthenticated requests in such a scenario, you can simply throw an exception from the OnTokenRequestFailed event:

services
    .AddHttpClient("client-for-my-downstream-api")
    .AddOidcClientCredentials(options =>
    {
        // normal configuration...
        options.Events.OnTokenRequestFailed = 
            (tokenResponse) => throw new InvalidOperationException($"Token request failed: {tokenResponse.Error}");
    })
;

In such a case, the exception will bubble up to the HttpClient.SendAsync() call site.

aspnetcore.noninteractiveoidchandlers's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

aspnetcore.noninteractiveoidchandlers's Issues

Multiple Typed HttpClients sharing the same token

Hi,

Is there a way to share tokens between multiple Typed HttpClients that use the same authentication configuration?

Here's an example of my code:

void ConfigureClientCredentialsTokenHandlerOptions(ClientCredentialsTokenHandlerOptions options)
{
	options.Authority = Configuration.Auth0.Authority;
	options.ClientId = Configuration.Auth0.ClientId;
	options.ClientSecret = Configuration.Auth0.ClientSecret;
	options.Scope = "customers suppliers";
	options.GrantType = Configuration.Auth0.GrantType;
	options.EnableCaching = true;
}

services.AddHttpClient<ICustomerClient, CustomerClient>(
	client => client.BaseAddress = new Uri(Configuration.BaseUri)
).AddOidcClientCredentials(ConfigureClientCredentialsTokenHandlerOptions);

services.AddHttpClient<ISupplierClient, SupplierClient>(
	client => client.BaseAddress = new Uri(Configuration.BaseUri)
).AddOidcClientCredentials(ConfigureClientCredentialsTokenHandlerOptions);

In this case a token is fetched and cached per HttpClient. But I'm looking for a solution where the token is only fetched once.

Regards,

Jochen

Incompatible with IdentityModel 6.0.0

Hi,

AspNetCore.NonInteractiveOidcHandlers does not work when installing the latest version of IdentityModel.

The following error occurs when trying to use the GetToken method on the ClientCredentialsTokenHandler.

image

Kind regard,
Jorsi

An item with the same key has already been added. Key: scope

When caching is not enabled, the following error occurs: An item with the same key has already been added. Key: scope

The cause is the following line:

Parameters = _options.ExtraTokenParameters ?? new Dictionary<string, string>(),

My configuration uses:

options.ExtraTokenParameters = new Dictionary<string, string> {{"audience", Configuration.Audience}};

The issue is that the Dictionary is reused and thus the second time, keys that already part of the dictionary are added again.

The solution is to create a new dictionary based on the dictionary in the options.

Parameters = _options.ExtraTokenParameters ? new Dictionary<string, string>(_options.ExtraTokenParameters) :  new Dictionary<string, string>(),

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.