OAuth 2.0 vs OpenID Connect in .NET: choosing a flow, client credentials with MSAL

ASP.NET Core Interview Q&A November 13, 2026 csharp

OAuth 2.0 is about authorization: it issues an access token for accessing an API.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// ======================================================================
// Q: OAuth 2.0 vs OpenID Connect. Which flow do you use when?
//
// OAuth 2.0 is about authorization: it issues an access token for accessing an
// API. OpenID Connect builds on top of it and adds authentication: the id_token
// says who the user is.
// - Authorization Code + PKCE: users (web apps, SPA, mobile). The default today.
// - Client Credentials: service-to-service without a user: background jobs,
//   integration with Salesforce or an ERP.
// - Device Code: devices without a browser (CLI, IoT, e-mobility chargers).
// - Implicit, Password (ROPC): deprecated, we don't use them.
// Between our own services in Azure: Managed Identity, with no secret at all.
// ======================================================================

// Client credentials to an external API, with token caching (MSAL keeps a cache, so singleton)
builder.Services.AddSingleton(sp =>
{
    var o = builder.Configuration.GetSection("Erp").Get<ErpAuthOptions>()!;
    return ConfidentialClientApplicationBuilder.Create(o.ClientId)
        .WithClientSecret(o.ClientSecret)              // from Key Vault (Q15). Better: certificate
        .WithAuthority(o.Authority)
        .Build();
});

public class ErpTokenHandler(IConfidentialClientApplication msal, IOptions<ErpAuthOptions> opt) : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage req, CancellationToken ct)
    {
        var result = await msal.AcquireTokenForClient([opt.Value.Scope]).ExecuteAsync(ct);   // from cache if still valid
        req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
        return await base.SendAsync(req, ct);
    }
}
builder.Services.AddTransient<ErpTokenHandler>();
builder.Services.AddHttpClient<IErpClient, ErpClient>().AddHttpMessageHandler<ErpTokenHandler>();