Securing your .NET Core API with AWS Cognito

Securing your .NET Core API with AWS Cognito

This is the first article in a series where I'm planning to have a look at Authentication & Authorization, using Cloud Providers, in .NET Core.

In this article we'll first have a look at Setting up and connecting to AWS Cognito.

What will be covered in this article:

  • Creating a User Pool in AWS.
  • Adding the necessary nuget packages.
  • Writing the controller methods to login & change password.

I'm assuming that most reading this article has some level of experience with creating projects in Visual Studio IDE, and will not be covering that step by step. All the source code is available on my github, and will be updated as we progress with the articles.

AWS Cognito

In order to setup the User Pool you will need an AWS Console account. If you do not have one already you can signup here.

Setting up tine User Pool

Setup a User Pool using the AWS console.

No alt text provided for this image

Select Review Defaults

No alt text provided for this image

Select Add app client

No alt text provided for this image

Create the App Client

No alt text provided for this image

Make sure to select the options as per the screen shot above. (I did find that I had to go back and reapply some of the selections)

Get AWS Ids & Secrets

Under the General setting tab we'll find the User Pool Id

No alt text provided for this image
No alt text provided for this image

Copy the Pool Id, as we'll need that to connect later.

Under the App clients tab we'll find the App Client Id & the App Client Secret (Note: Keep these secure)

No alt text provided for this image
No alt text provided for this image

While on the App client setting confirm that the Auth Flows Configuration settings are correct:

No alt text provided for this image

Visual Studio Project

We begin by creating a new project using the ASP.NET Core Web API template.

Nuget Packages

The following nuget packages needs to be install in the project:

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 5.0.14

Install-Package Amazon.AspNetCore.Identity.Cognito

Install-Package Amazon.Extensions.CognitoAuthentication

Install-Package AWSSDK.CognitoIdentityProvider        

App Settings

In development the settings can be configure in appsettings.Development.json and for the purposes of this example that is where we will set them; however is is not recommended or in any way best practice to store secrets in app setting. Rather make use of secrets management.

Add the following to the appsettings.Development.json file.

"AWS": {
? ? "Region": "<your region id goes here>",
? ? "UserPoolClientId": "<your user pool client id goes here>",
? ? "UserPoolClientSecret": "<your user pool client secret goes here>",
? ? "UserPoolId": "<your user pool id goes here>"
}        

Replace all the values with the Id's & Keys from AWS. The region id can be found in the Cognito url or in the region selector in AWS Console.

No alt text provided for this image

The Code

Firstly we need to setup Cognito in our Startup.cs file

ConfigureServices Method:

public void ConfigureServices(IServiceCollection services)
{
    // Adds Amazon Cognito as Identity Provider
    services.AddCognitoIdentity();
        
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.Authority = $"https://cognito-idp.{this.config.AWS.Region}.amazonaws.com/{this.config.AWS.UserPoolId}";
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = $"https://cognito-idp.{this.config.AWS.Region}.amazonaws.com/{this.config.AWS.UserPoolId}",
            ValidateLifetime = true,
            LifetimeValidator = (before, expires, token, param) => expires > DateTime.UtcNow,
            ValidateAudience = false,
        };
     });
     ...
}        

Configure Method

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // If not already enabled, you will need to enable ASP.NET Core authentication
    app.UseAuthentication();
    ...
}        

The Models

For now we'll need 3 Models that will be used in the controller methods.

The User Login Model:

public class UserLoginModel
{
? ? public string Email { get; set; }

? ? public string Password { get; set; }
}        

The Update Password Model

public class UpdatePasswordModel : UserLoginModel
{
? ? public string NewPassword { get; set; }

}        

The Token Response Model

public class TokenResponseModel
{
? ? public string RefreshToken { get; set; }
? ? ? ??
? ? public string Token { get; set; }

? ? public DateTimeOffset Expiry { get; set; }
}        

The Controller

In the controllers folder add a new controller called AccountController.

We'll make use of constructor Dependency Injection to get access to the Identity Provider, User Pool & Cognito Configuration.

public AccountController(IAmazonCognitoIdentityProvider identityProvider, CognitoUserPool userPool, AwsCognitoConfig cognitoConfig)        

We'll add two methods to the controller, LoginAsync & ChangePasswordAsync.

LoginAsync

[HttpPost]
[Route("login")]
public async Task<TokenResponseModel> LoginAsync([FromBody]UserLoginModel model)
{
? ? var user = new CognitoUser(
? ? ? ? model.Email,
? ? ? ? this.cognitoConfig.UserPoolClientId,
? ? ? ? this.userPool,
? ? ? ? this.identityProvider,
? ? ? ? clientSecret: this.cognitoConfig.UserPoolClientSecret
? ? );
? ? var authRequest = new InitiateSrpAuthRequest()
? ? {
? ? ? ? Password = model.Password,
? ? };
? ? var authResponse = await user.StartWithSrpAuthAsync(authRequest);


? ? if (authResponse.ChallengeName == ChallengeNameType.NEW_PASSWORD_REQUIRED)
? ? {
? ? ? ? throw new Exception("Update your temporary password.");
? ? }


? ? var timespan = TimeSpan.FromSeconds(authResponse.AuthenticationResult.ExpiresIn);
? ? var expiry = DateTimeOffset.UtcNow + timespan;


? ? return new TokenResponseModel
? ? {
? ? ? ? RefreshToken = authResponse.AuthenticationResult.RefreshToken,
? ? ? ? Token = authResponse.AuthenticationResult.AccessToken,
? ? ? ? Expiry = expiry,
? ? };
}
        

ChangePasswordAsync

[HttpPost]
[Route("updatepassword")]
public async Task<string> ChangePasswordAsync([FromBody]UpdatePasswordModel model)
{
	var user = new CognitoUser(
		model.Email,
		this.cognitoConfig.UserPoolClientId,
		this.userPool,
		this.identityProvider,
		clientSecret: this.cognitoConfig.UserPoolClientSecret
	);
	var authRequest = new InitiateSrpAuthRequest()
	{
		Password = model.Password,
	};
	var authResponse = await user.StartWithSrpAuthAsync(authRequest);
	var response = await user.RespondToNewPasswordRequiredAsync( new RespondToNewPasswordRequiredRequest
	{
		SessionID = authResponse.SessionID,
		NewPassword = model.NewPassword,
	});


	return "Password changed successfully.";
}        

With that we can now use the Authorize attribute on the controller classes.

In order for the token to be used in the Swagger definition we need to setup swagger that it know about the authentication and how it should be applied.

In the ConfigureServices method in Startup.cs add the following code:

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "CognitoSampleApi", Version = "v1" });


    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
        In = ParameterLocation.Header,?
        Description = "Please enter into field the word 'Bearer' followed by a space and the JWT value",
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey?
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement {
        {?
            new OpenApiSecurityScheme?
            {?
                Reference = new OpenApiReference?
                {?
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"?
                }?
            },
            new string[] { }?
        }?
    });
});        

Running the App

When we now run the application we get the following swagger display:

No alt text provided for this image

We can now use the login endpoint the get the JWT token that can be used to Authorize the protected API calls.

Conclusion

Working with AWS Cognito makes it easy to implement authentication, that is secure, in your system. Additionally methods for create user, forgot password, refresh token etc. can also be implemented, that I'll leave for another article, which will build on this existing code.

All the source code is available on my github. Feel free to head over there and download the code.

要查看或添加评论,请登录

Francois de Wet的更多文章

社区洞察

其他会员也浏览了