Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autorenew OAuth Tokens #842

Open
wants to merge 20 commits into
base: dev
Choose a base branch
from

Conversation

SemaphoreSlim1
Copy link

@SemaphoreSlim1 SemaphoreSlim1 commented Oct 7, 2024

Resolves #678

Flurl has a way to add bearer tokens to the authentication header via .WithOAuthBearerToken, but it's on us, the users of Flurl to retrieve, cache, and renew those tokens.

This pull request adds that functionality by adding an IOAuthTokenProvider and a set of OAuth scopes to the settings. The token provider is responsible for making the call to retrieve the token, but only when the token is expired. The scopes are set separately so that callers can vary their scopes to the section of code/specific requests where appropriate. However, if all requests require the same scope(s), then you can also set it at the same time you set the token provider.

A client credentials provider for identity server is provided. If your token provider has different requirements/payloads/conventions/etc, you can use it as a reference implementation to create your own token provider.

Usage:
Register the token provider as a singleton, otherwise the caching mechanism wont work:

// ClientID/ClientSecret
services.AddSingleton<IOAuthTokenProvider>(sp =>{
   var clientId = "yourClientId";
   var clientSecret = "yourClientSecret";

   var fc = new FlurlClientBuilder("https://yourIdentityServerBaseAddress.com") 
                        .Build();

   return new ClientCredentialsTokenProvider(clientId, clientSecret, fc);   
}
// OR....
// certificate based authentication
services.AddSingleton<IOAuthTokenProvider(sp =>{
  var clientId = "yourClientId"
  var cert = //retrieve your X509 Certificate

   var fc = new FlurlClientBuilder("https://yourIdentityServerBaseAddress.com") 
                       .ConfigureInnerHandler(handler =>{
                          handler.ClientCertificates.Add(cert);
                        }
                        .Build();

   return new ClientCredentialsTokenProvider(clientId, clientSecret: null, fc);
});

When creating a client:

IFlurlClientCache cache = //instantiate or get from FlurlHttp.Clients

var tokenProvider = serviceProvider.GetRequiredService<IOAuthTokenProvider>();
cache.Add("yourClientName", "https://myapi.com", builder => {
  builder.WithOAuthTokenProvider(tokenProvider);
});

When making a request

  IFlurlClient fc = //however you resolve the flurl client

  fc.Request("your","path")
     .WithOAuthTokenFromProvider(scope: "your:scope")
     .... //complete your request
 ```

@SemaphoreSlim1
Copy link
Author

SemaphoreSlim1 commented Oct 7, 2024

Common questions I see out of this:

Q: What if my identity provider needs {custom payload/custom request/custom endpoint}?
A: Inherit from OAuthTokenProvider and implement however you see fit. See ClientCredentialsTokenProvider as a reference implementation.

Q: What if I need to work with more than 1 identity provider in my project
A: Register the token providers as a Keyed Service Singleton, and resolve accordingly. This feature is usually called "named registration" in other containers.

Q: What if I just want to set the token provider and scope once, at service registration time, and not at time-of-use?
A: Effectively, both extension methods are a pass through to some additional properties on IFlurlSettings - so you can set this with other properties while using .WithSettings, or you can add it on anything that implements an ISettingsContainer, like a FlurlClientBuilder, FlurlClient, or a FlurlRequest.

Q: What if I can't set these in my startup/program.cs, and I have to set both at time-of-use?
A: See the above answer

Q: What if I need to accommodate for clock skew and artificially expire my tokens early?
A: There's a constructor parameter on OAuthTokenProvider and ClientCredentialsTokenProvider which allows you to artificially expire tokens earlier than the expiration time that's returned from the identity management service. If you omit that parameter, or set it to null when instantiating, it'll default to zero, and take the expiration time returned from the service at face value.

Q: My service issues "OAuth" tokens, not "Bearer" tokens - how can I set the scheme?
A: That's also a constructor parameter on the token providers. Omitting that parameter will default it to "Bearer", but if you need it to be "OAuth" or something else, just set it on instantiation.

Q: Does renewing a token block all requests using that provider?
A: No. The semaphore which governs token renewal is set at the scope level - so you're only blocked when renewing a token for a given scope. In a multithreaded scenario attempting to access two scopes and one happens to still be valid, only the one that is invalid blocks until it is renewed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Autorenew OAuth token
1 participant