Implement an OAuth 2.0 Server (Part 14)

Welcome to the fourteenth part of a series of posts where we will implement an OAuth 2 Server using AspNet.Security.OpenIdConnectServer.

Adding OAuth Validation

Our server successfully issues the three token types, but we don’t have a way to actually force a token check on our endpoints yet. One of the packages we downloaded initially was AspNet.Security.OAuth.Validation. We’re going to use that to add OAuth Validation to our server.

In Startup.cs's ConfigureServices, add .AddOAuthValidation() to the Authentication chain:

 1// This method gets called by the runtime. Use this method to add services to the container.
 2public void ConfigureServices(IServiceCollection services)
 3{
 4    services.AddDbContext<ApplicationDbContext>(options =>
 5        options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
 6
 7    services.AddIdentity<ApplicationUser, IdentityRole>((x) => {
 8        x.Password.RequiredLength = 6;
 9        x.Password.RequiredUniqueChars = 0;
10        x.Password.RequireNonAlphanumeric = false;
11        x.Password.RequireDigit = false;
12        x.Password.RequireLowercase = false;
13        x.Password.RequireUppercase = false;
14    })
15        .AddEntityFrameworkStores<ApplicationDbContext>()
16        .AddDefaultTokenProviders();
17
18    services.AddAuthentication()
19        .AddOAuthValidation()
20        .AddOpenIdConnectServer(options => {
21            options.UserinfoEndpointPath = "/api/v1/me";
22            options.TokenEndpointPath = "/api/v1/token";
23            options.AuthorizationEndpointPath = "/authorize/";
24            options.UseSlidingExpiration = false; // False means that new Refresh tokens aren't issued. Our implementation will be doing a no-expiry refresh, and this is one part of it.
25            options.AllowInsecureHttp = true; // ONLY FOR TESTING
26            options.AccessTokenLifetime = TimeSpan.FromHours(1); // An access token is valid for an hour - after that, a new one must be requested.
27            options.RefreshTokenLifetime = TimeSpan.FromDays(365 * 1000); //NOTE - Later versions of the ASOS library support `TimeSpan?` for these lifetime fields, meaning no expiration. 
28                                                                            // The version we are using does not, so a long running expiration of one thousand years will suffice.
29            options.AuthorizationCodeLifetime = TimeSpan.FromSeconds(60);
30            options.IdentityTokenLifetime = options.AccessTokenLifetime;
31            options.ProviderType = typeof(OAuthProvider);
32        });
33
34    // Add application services.
35    services.AddTransient<IEmailSender, EmailSender>();
36    services.AddScoped<OAuthProvider>();
37    services.AddTransient<ValidationService>();
38    services.AddTransient<TokenService>();
39
40    services.AddMvc();
41}

If you want to specify a different Authentication scheme for the OAuth validation, for instance if you have two different areas of your server performing OAuth validation on a different set of users, then you can specify the scheme by supplying a string in the method call. We will leave it blank because we’re fine with the default.

You can read more about Authentication Schemes on the Microsoft Docs.

Adding Authentication Schemes to our App

We’ve left the OAuth Validation scheme as the default, but when we add endpoint authorization, we will need to specify what we’re using - if we leave it as the default, the server will attempt to use a cookie identity, and will promptly fail because an incoming request with an OAuth token won’t have the necessary cookie information.

Open up Controllers/APIController.cs and add some Authorization attributes:

 1// Authenticated Methods - only available to those with a valid Access Token
 2// Unscoped Methods - Authenticated methods that do not require any specific Scope
 3[Authorize(AuthenticationSchemes = AspNet.Security.OAuth.Validation.OAuthValidationDefaults.AuthenticationScheme)]
 4[HttpGet("clientcount")]
 5public async Task<IActionResult> ClientCount() {
 6    return Ok("Client Count Get Request was successful but this endpoint is not yet implemented");
 7}
 8
 9// Scoped Methods - Authenticated methods that require certain scopes
10[Authorize(AuthenticationSchemes = AspNet.Security.OAuth.Validation.OAuthValidationDefaults.AuthenticationScheme)]
11[HttpGet("birthdate")]
12public IActionResult GetBirthdate() {
13    return Ok("Birthdate Get Request was successful but this endpoint is not yet implemented");
14}
15
16[Authorize(AuthenticationSchemes = AspNet.Security.OAuth.Validation.OAuthValidationDefaults.AuthenticationScheme)]
17[HttpGet("email")]
18public async Task<IActionResult> GetEmail() {
19    return Ok("Email Get Request was successful but this endpoint is not yet implemented");
20}
21
22[Authorize(AuthenticationSchemes = AspNet.Security.OAuth.Validation.OAuthValidationDefaults.AuthenticationScheme)]
23[HttpPut("birthdate")]
24public IActionResult ChangeBirthdate(string birthdate) {
25    return Ok("Birthdate Put successful but this endpoint is not yet implemented");
26}
27
28[Authorize(AuthenticationSchemes = AspNet.Security.OAuth.Validation.OAuthValidationDefaults.AuthenticationScheme)]
29[HttpPut("email")]
30public async Task<IActionResult> ChangeEmail(string email) {
31    return Ok("Email Put request received, but function is not yet implemented");
32}
33
34// Dynamic Scope Methods - Authenticated methods that return additional information the more scopes are supplied
35[Authorize(AuthenticationSchemes = AspNet.Security.OAuth.Validation.OAuthValidationDefaults.AuthenticationScheme)]
36[HttpGet("me")]
37public async Task<IActionResult> Me() {
38    return Ok("User Profile Get request received, but function is not yet implemented");
39}

We’ve added a standard [Authorize] attribute to our endpoints, but we’ve told it what AuthenticationScheme to use. We left the scheme name blank in ConfigureServices, so it chose the default value of AspNet.Security.OAuth.Validation.OAuthValidationDefaults.AuthenticationScheme. If you change the ConfigureServices default scheme name, you should reflect it here in the endpoints.

Moving On

The demo of this project to this point can be found here on GitHub.

In the next section, we’ll cover adding policies, dynamically resolving them, and adding them to our endpoints.

Next

Posts in this series