I have an ASP.NET Core MVC application allowing anonymous users. This app is calling an ASP.NET Web API that is protected by Identity Server 4. I have created a client in Identity Server describing the MVC app (client) and given it access to the api scope like this:
new Client
{
ClientId = "my-mvc-client-app",
AllowedGrantTypes = GrantTypes.ClientCredentials,
RequireConsent = false,
ClientSecrets = new List<Secret> { new Secret("this-is-my-secret".Sha256()) },
AllowedScopes = new List<string>
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.OfflineAccess.Name,
"my-protected-api"
},
RedirectUris = new List<string>
{
"http://localhost:5009/signin-oidc",
}
}
In my MVC app, I'm using TokenClient
to get a token that I can use when making requests to the protected API like this:
var disco = await DiscoveryClient.GetAsync("http://localhost:5010");
var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, clientSecret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("hrmts-test-candidate-api-scope");
This works fine, but I'm requesting new tokens from Identity Server on every request, which is probably not a good idea.
What is the best practice for handling the tokens? How can I persist them on the client (the MVC app) and how can I handle refresh tokens to make sure the client gets a new token when necessary?
You need to wrap that client in a managed service of some kind (as a singleton) so that you can use it anywhere you need. We have a token component that we use for server to server communication that follows this flow:
public class ServerTokenComponent
{
private TokenResponse Token { get; set; }
private DateTime ExpiryTime { get; set; }
public async Task<TokenResponse> GetToken()
{
//use token if it exists and is still fresh
if (Token != null && ExpiryTime > DateTime.UtcNow)
{
return Token;
}
//else get a new token
var client = new TokenClient("myidpauthority.com","theclientId","thesecret")
var scopes = "for bar baz";
var tokenResponse = await client.RequestClientCredentialsAsync(scopes);
if (tokenResponse.IsError || tokenResponse.IsHttpError)
{
throw new SecurityTokenException("Could not retrieve token.");
}
//set Token to the new token and set the expiry time to the new expiry time
Token = tokenResponse;
ExpiryTime = DateTime.UtcNow.AddSeconds(Token.ExpiresIn);
//return fresh token
return Token;
}
}