static void

OAuth 2

There is a central Authorization Server (AS)/Identity Provider (IDP). This may be an internal server (eg ADFS), or an eternal one (Azure). It manages resources and scopes, and publishes a public discovery metadata (/.well-known/openid-configuration). This includes the public key, urls to get tokens, scopes etc.

The IDP issues JWT (jason web token/"jot") tokens. This is a base64 encoded string (not secure from reading), but includes a footer section which is signed by the issuer (verify with the public key) so it is tamper-proof. This token can be passed as a bearer token between applications/browser-api in the http Authorization header (Authorization: Bearer xxxx).

Yes, the Authorization header is actually authentication. See also in the apicontroller the Authorize() attribute /.RequireAuthorization() which is actually authentication. If you include policies it includes authorization (checking specific scopes).

The terminology for the api/application is confusing. Resource server is the API with protected resources. Resource owner is the client/application using it.

Microsoft (Azure AD) extends OAuth with audience (rather than just scope) and mapInboundClaims (renaming claims), which can be confusing.

Tokens

Access tokens are like cookies in traditional forms authentication:

You should normally request scopes when explicitly needed, not all in advance. The authorization API should include a "include_granted_scopes=true" to roll up previously granted scopes.

OpenId (with scope "openid" plus "profile" and others) adds an id_token in addition to an access (bearer) token with user claims, which is put into the cookie.

With scope "offline_access" we can get a refresh_token, which is effectively a keep-alive for a token.

Flows

As of OAuth 2.1 there are only 2 recommended flows (others such as Password and Implicit flow are obsolete)

Google's OAuth documentation

Generally you have to register the client with the auth server (so it knows where to redirect, or at least validate your redirect_uri).

Authorization code (Browsers/Apps)

Authorization Code Flow (Server-side)

Client Credential (application-level access)

OpenID Connect

OpenID Connect is an authentication protocol built on top of OAuth2.

In Startup do the following:

//always do cookie middleware first
 app.UseCookieAuthentication(new CookieAuthenticationOptions
 {
     AuthenticationType = "Cookies",
 });

 app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
 {
     Authority = "https://localhost:44324/core"//IdP Url,
     ClientId = "tst-infrabel.be"//OpenID server must have this client
     Scope = "openid profile roles"//what roles I want (default is openid)

     RedirectUri = "https://localhost:44348/"//start page for this application
     ResponseType = "code id_token token",

     SignInAsAuthenticationType = "Cookies",
     Notifications = new OpenIdConnectAuthenticationNotifications
     {
         AuthenticationFailed = context =>
                                {
                                    //logging errors
                                    Console.WriteLine(context.Exception);
                                    return Task.FromResult(0);
                                },
         //claims transformation
         SecurityTokenValidated = notification =>
         {
             var id = notification.AuthenticationTicket.Identity;

             // create new identity and set name and role claim type
             var nid = new ClaimsIdentity(id.AuthenticationType);
             //copy the claims in
             nid.AddClaim(id.FindFirst(ClaimTypes.Name));
             nid.AddClaims(id.FindAll(ClaimTypes.Role));
             // add new claim
             nid.AddClaim(new Claim("LogonTime"DateTime.UtcNow.ToString("u")));

             notification.AuthenticationTicket =
                 new AuthenticationTicket(nid, notification.AuthenticationTicket.Properties);

             return Task.FromResult(0);
         }
     }
 });

The client must use SSL. If you don't (e.g. local development), the client will enter an infinite redirect loop back to the IdP (you can also have this if the client URL doesn't end in "/").
In app.UseCookieAuthentication(new CookieAuthenticationOptions add:
CookieSecure = CookieSecureOption.Never
Obviously don't leave this is production- use the [RequireHttps] attribute.

SignOut

Use Request.GetOwinContext().Authentication.SignOut();

Client

A simple console client

static void Main(string[] args)
{
    var token = GetToken().Result;
    Console.WriteLine(token);
 
    var data = CallService(token).Result;
    Console.WriteLine(data);
}
 
private async static Task<string> GetToken()
{
    using (var client = new HttpClient())
    {
        var post =
            new Dictionary<string, string>
            {
                {"grant_type", "password"},
                {"username", "alice"},
                {"password", "secret"},
                //client
                {"client_id", "1"},
                {"client_secret", "secret"},
            };
 
        var response = await client.PostAsync("http://localhost:4746/token",
            new FormUrlEncodedContent(post));
        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
 
            var json = JObject.Parse(content);
            return json["access_token"].ToString();
        }
        throw new InvalidOperationException(response.ReasonPhrase);
    }
}
 
private async static Task<string> CallService(string token)
{
    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", token);
 
        return await client.GetStringAsync("http://localhost:4746/api/data");
    }
}