Part 4 - Create Custom Authorization Handler

In this task you will create a number of classes that represent the implementation of custom authorization handler logic leveraging Cognito

1. There’s a few more places to remove references to S3, from the Solution Explorer expand Dependencies -> NuGet and right-click AWSSDK.S3 and select Remove

2. From the Solution Explorer open the Startup.cs file and delete line 15 as shown below.

3. Still in the Startup.cs file let’s delete lines 28 & 29 as shown below.

4. From the Build menu select Rebuild Solution to ensure all the changes made so for are accurate.

5. From the Solution Explorer right-click the project and select -> Add

-> Class

6. Name the class CognitoGroupAuthorizationRequirement.cs and select Add

7. Make the following code changes to the class

  1. Add the following line to the using section:

    using Microsoft.AspNetCore.Authorization;
    
  2. Replace the class with the following:

    class CognitoGroupAuthorizationRequirement : IAuthorizationRequirement
    {
    
    	public string CognitoGroup { get; private set; }
    
    	public CognitoGroupAuthorizationRequirement(string cognitoGroup)
    
    	{
    
    		CognitoGroup = cognitoGroup;
    
    	}
    
    }
    

The class should look like:

8. Let’s add another class (right-click the Project -> Add -> Class) and name the class CognitoGroupAuthorizationHandler.cs and add the following code:

  1. Add the following line to the using section:

    using Microsoft.AspNetCore.Authorization;
    
  2. Replace the class with the following:

    	class CognitoGroupAuthorizationHandler : AuthorizationHandler<CognitoGroupAuthorizationRequirement>
    	{
    
    		protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CognitoGroupAuthorizationRequirement requirement)
    
    		{
    
    			if (context.User.HasClaim(c => c.Type == "cognito:groups" && c.Value == requirement.CognitoGroup))
    			{
    
    				context.Succeed(requirement);
    			}
    			else
    			{
    				context.Fail();
    			}
    
    			return Task.CompletedTask;
    		}
    
    	}
    

The class should look like:

Info: Custom handler just checks to see if the Cognito group is equal to whatever group you specify in the Authorization Policy. In the next step you will be adding Authorization Policy.

9. Let’s jump back to the Startup.cs file and add the following to the

using section:

  1. Add the following line to the using section:

    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    

The using section should look like:

10. Still in the Startup.cs file let’s replace the code for the public void ConfigureServices (IServiceCollection services) class with the following:

// This method gets called by the runtime. Use this method to add services to the container

public void ConfigureServices(IServiceCollection services)
{

	// add our Cognito group authorization requirement, specifying CalendarWriter as the group
	services.AddAuthorization( options =>
		{
			options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build();
			options.AddPolicy("InCalendarWriterGroup", policy => policy.Requirements.Add(new CognitoGroupAuthorizationRequirement("CalendarWriter")));
		});

	// add a singleton of our cognito authorization handler

	services.AddSingleton<IAuthorizationHandler, CognitoGroupAuthorizationHandler>();
	services.AddAuthentication(options =>
	{
		options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
		options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
	}).AddJwtBearer(o =>
	{
		o.Audience = "<the app ID you copied from Cognito earlier>";
		o.Authority = "https://cognito-idp.<region>.amazonaws.com<User Pool Id>";
		o.RequireHttpsMetadata = false;
	});

	services.AddMvc();

	// Pull in any SDK configuration from Configuration object

	services.AddDefaultAWSOptions(Configuration.GetAWSOptions());

}

The class should look like:

Note: For production app, you could pass Authority and Audience values as Environment Variables at deploy time, then read the values with Environment.GetEnvironmentVariable

Note: For testing locally without having to worry about certificates, set RequireHttpsMetadata to false. For production app, RequireHttpsMetadata should set this to true so that the JWT token is only sent over HTTPS. The JwtBearer middleware validates the JWT’s digital signature, checks the expiration and does other checks, then creates a ClaimsPrincipal for the current user

11. We need to modify a couple of properties in the code with values noted in previous steps, grab the Cognito Pool Id from step 1.9 and the Cognito App client id from step 1.10 and the AWS Region in which you created the Cognito User Pool, in my case these values are:

  • Cognito User pool id: us-east-2_P9uuQFDnM

  • Cognito App client id: au93epsabvgh4hki86akq324f

  • Cognito Region: us-east-2

Before we update the following lines in the ConfigureServices class we’ll need to construct the URL that we’ll use in the o.Authority property:

How do we construct the o.Authority value?

  1. We start with: “https://cognito-idp..amazonaws.com/

  2. Let’s append the User pool id to the end of the URL: “https://cognito-idp..amazonaws.com/us-east-2_P9uuQFDnM

In my example that gives me:

o.Authority = “https://cognito-idp.us-east-2.amazonaws.com/us-east-2_P9uuQFDnM";

And for the o.Audience property we’ll use the Cognito App cleint id:

o.Audience = “au93epsabvgh4hki86akq324f”;

Note: if your region is Oregon (us-west-2) and your User Pool Id was us-west-2_AbcDefGhi, your Authority string would be https://cognito-idp.us-west-2.amazonaws.com/us-west-2_AbcDefGhi

The class should now look like:

Info:The ConfigureServices(IServiceCollection services) class is responsible for doing the following:

Registers your policy and adds a singleton of the handler class

Configures the JWT authentication options

We are hard-coding the o.Audience (the app client ID from Cognito) and o.Authority (based on User Pool ID) values for simplicity’s sake in the walkthrough

12. Staying in the Startup.cs file let’s replace Configure method with the following code:

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{

	loggerFactory.AddLambdaLogger(Configuration.GetLambdaLoggerOptions());

	app.UseDefaultFiles(); //needs to be before the app.UseStaticFiles() call below

	app.UseStaticFiles();

	app.UseAuthentication();

	app.UseMvc();

}

Info: This code enables app to serve static files (like html, css and JS) as well default files, so you don’t have to specify index.html in the URL of the main page

The method should look like: