A typical architecture is composed of two application (aka client) types - machine to machine calls and interactive applications.
In this scenario a headless application with no interactive user (e.g. a server daemon, batch job etc.) wants to call an API.
Prerequisites are:
According to the OAuth specification, you request a token by posting to the token endpoint:
POST /connect/token
CONTENT-TYPE application/x-www-form-urlencoded
client_id=client1&
client_secret=secret&
grant_type=client_credentials&
scope=scope1
In the success case, this will return a JSON response containing the access token:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "bearer",
"expires_in": 3600,
}
In .NET you can leverage the IdentityModel client library to request tokens.
The above token request would look like this in C#:
using IdentityModel.Client;
var client = new HttpClient();
var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = "https://demo.duendesoftware.com/connect/token",
ClientId = "m2m",
ClientSecret = "secret",
Scope = "api"
});
The Duende.AccessTokenManagement library can automate client credential request and token lifetime management for you.
Using this library, you only need to register the token client in DI:
builder.Services.AddAccessTokenManagement(options =>
{
options.Client.Clients.Add("client", new ClientCredentialsTokenRequest
{
Address = "https://demo.duendesoftware.com/connect/token",
ClientId = "m2m",
ClientSecret = "secret",
Scope = "api"
});
});
You can then add token management to an HTTP-factory provided client:
builder.Services.AddClientAccessTokenClient("client", configureClient: client =>
{
client.BaseAddress = new Uri("https://demo.duendesoftware.com/api/");
});
…and finally use the client with automatic token management in your application code:
public class DataController : Controller
{
IHttpClientFactory _factory;
public DataController(IHttpClientFactory factory)
{
_factory = factory;
}
public IActionResult Index()
{
var client = _factory.CreateClient("client");
// rest omitted
}
}
In this scenario, an interactive application like a web application or mobile/desktop app wants to call an API in the context of an authenticated user (see spec here).
You will receive three tokens - an identity token containing details about the end-user authentication, the access token to call the API, and a refresh token for access token lifetime management. The access token will also contain some information about the end-user (e.g. the user ID), so that the API can do authorization based on the user’s identity.
In this scenario you typically use the authorization code flow which first involves a call to the authorize endpoint for all human interactions (e.g. login and/or consent). This returns a code, which you then redeem at the token endpoint to retrieve identity and access tokens.
Prerequisites are:
The call to the authorize endpoint is done using a redirect in the browser:
GET /connect/authorize?
client_id=client1&
scope=openid api1&
response_type=code&
redirect_uri=https://myapp/callback&
On success, the browser will ultimately redirect to the callback endpoint transmitting the authorization code (and other parameters like the granted scopes):
GET /callback?
code=abc&
scope=openid api1
The client then opens a back-channel communication to the token service to retrieve the tokens:
POST /connect/token
CONTENT-TYPE application/x-www-form-urlencoded
client_id=client1&
client_secret=secret&
grant_type=authorization_code&
code=abc&
redirect_uri=https://myapp/callback
In this scenario, the token response will contain three tokens:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"id_token": "...",
"access_token": "...",
"refresh_token": "...",
"token_type": "bearer",
"expires_in": 3600,
}
See the refresh token section for more information on how to deal with refresh tokens.
The most common client library for .NET is the OpenID Connect authentication handler for ASP.NET Core. This library handles the complete front- and back-channel interaction and coordination.
You only need to configure it in your startup code:
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "duende";
})
.AddCookie("cookie")
.AddOpenIdConnect("duende", "IdentityServer", options =>
{
options.Authority = "https://demo.duendesoftware.com";
options.ClientId = "interactive.confidential";
options.ResponseType = "code";
options.ResponseMode = "query";
options.SaveTokens = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("api");
options.Scope.Add("offline_access");
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
// Disable x-client-SKU and x-client-ver headers
options.DisableTelemetry = true;
});
The Duende.AccessTokenManagement library can also be used to automate token lifetime management in ASP.NET Core applications for you.