Monday 20 February 2023

User Authentication with AWS Cognito with Web API Backend and Frontend Web
Part 3 - Authentication between frontend and Web API backend

This is the last part of the series of setting up a simple Angular frontend web app with ASP.NET Core Web API backend (.NET 5.0) using AWS Cognito as authentication provider. Users will be able to sign up, confirm sign up/verify and sign in. The backend WebAPI will be the point of contact and interaction with AWS Cognito. The frontend Angular web app will simply pass user information and then keep the Cognito Access Token passed by the backend WebAPI.

This series is divided into three parts:
  1. Set up AWS Cognito and backend Web API user functions - previous post
  2. Configure CORS in Web API backend - previous post
  3. Authentication between frontend and Web API backend - this post

We have done our user sign in method that will return an Access Token to the caller (frontend). Now we only want authenticated users access our app. To do this we need to adjust our configurations a little bit.

In our backend Web API, add app.UseAuthentication() in Configure() method in Startup.cs between UseCors() and UseAuthorization():
public void ConfigureServices(IServiceCollection services)
{
	. . .
	app.UseCors();

	app.UseAuthentication();

	app.UseAuthorization();
	. . .
}
The order for the middlewares is specify in this ASP.NET Core documentation.


Next, add this line in our existing authentication configurations. We need to disable the default feature that is validating token audience as AWS Access Token does not include 'audience' (aud). So the frontend can present the token to backend without any issues.
public void ConfigureServices(IServiceCollection services)
{
	// AWS Cognito
	services.AddAuthentication("Bearer")
	.AddJwtBearer(options =>
	{
		options.Audience = "[APP_CLIENT_ID]";
		options.Authority = "https://cognito-idp.[REGION_NAME].amazonaws.com/[USER_POOL_ID]";
        options.TokenValidationParameters = new TokenValidationParameters { ValidateAudience = false };
	});

	. . .
}

To secure any methods, we just need to decorate with [Authorize] attribute. For example:
[Authorize]
public IEnumerable<WeatherForecast> Get()
{

	. . .
	
}

Then, in our frontend app, we create sign in functionality that will receive an access token once successful. The token is then stored in local storage that will be used for subsequent requests to the backend.
signin(): void {
  const params = new HttpParams()
    .set('username', this.model.Username)
    .set('password', this.model.Password);

  var user = this.model;

  this.http.post<any>('https://localhost:5001/api/signin', {username: this.model.Username, password: this.model.Password})
  .subscribe({
    next: token => {
      console.log(token);
      let atoken = token;
      localStorage.setItem("atoken", atoken);
    },
    error: error => {
      console.log(error);
    }
  })    
}

For further interaction with the backend, we retrieve the token and pass in request header:
getItems(): void {
  let token = localStorage.getItem("atoken");
  this.http.get("https://localhost:5001/weatherforecast", {
    headers: new HttpHeaders({
      "Authorization": "Bearer " + token
    })
  }).subscribe(response => console.log(response));
}

Now we have working frontend and backend that utilise AWS Cognito as authentication provider.