Friday, 10 November 2023

HTTP Interceptor Library for JSON Web Token Authorisation

I was looking for an interceptor library for HTTP requests made from my Angular web app to backend services. The idea is to inject every request with Authorization header containing user JWT access token. Found a popular one in GitHub, https://github.com/auth0/angular2-jwt.

First install the library:
npm install @auth0/angular-jwt

Then in app.module.ts file, add the codes below. Note that my app has an authentication class (AuthService) that manages all the authentication functionalities, including retrieving user JWT access token.
. . .
import { JwtModule, JWT_OPTIONS } from '@auth0/angular-jwt';
import { AuthService } from './services/auth.service';

export function jwtOptionsFactory(authService: AuthService) {
  return {
    tokenGetter: () => {
      return authService.getUserAccessToken();
    },
    allowedDomains: ["localhost:5555"]  // my local dev environment
  }
}


@NgModule({
  imports: [
    JwtModule.forRoot({
      jwtOptionsProvider: {
        provide: JWT_OPTIONS,
        useFactory: jwtOptionsFactory,
        deps: [AuthService]
      }
    }),
    . . .
  ],
  . . .
})

export class AppModule { }

Now all the HTTP requests will have 'Authorization: Bearer [TOKEN]' added in the headers.

Wednesday, 16 August 2023

Angular Jasmine Unit Test - Child Component with Input and Output Properties

We are going to write some unit tests for an Angular component that calls child components. Our main component template is as follow:
. . .
. . . some content of the main component . . .
. . .
<ng-container *ngFor="let id of studentIds">
  <app-student-profile [studentId]="id"  (alertEmitter)="displayAlert($event)></app-student-profile>
</ng-container>
. . .
Our child component:
<div *ngIf="student" class="studentDetails">
  <button id="btnTest" (click)="sendAlert()">send alert</button>
  <div>
    <div>Student ID</div>
    <div>{{student.studentId}}</div>
  </div>
  <div>
    <div>First Name</div>
    <div>{{student.firstName}}</div>
  </div>
  <div>
    <div>Last Name</div>
    <div>{{student.lastName}}</div>
  </div>
  <div>
    <div>Email Address</div>
    <div>{{student.email}}</div>
  </div>
</div>
The child component also has input and output properties. The input is expecting Student ID to be passed from the parent component and the output will pass a message to the parent component to be displayed.

Some of the codes from child component class:
. . .
@Input() studentId!: number;
@Output() alertEmitter: EventEmitter<string> = new EventEmitter<string>();

sendAlert(): void {
  this.alertEmitter.emit("an alert from student profile component with Student Id: " + this.studentId);
}
. . .

To test the child component, we can use ng-mocks testing library, which is popularly used for Angular testing. Our tests will look like:
describe('MainComponent', () => {
  let component: MainComponent;
  let childComponent: StudentComponent;
  let fixture: ComponentFixture<MainComponent>;

  beforeEach(async () => {

    await TestBed.configureTestingModule({
      declarations: [MainComponent, 
	                 MockComponent(StudentComponent)],
      //schemas: [NO_ERRORS_SCHEMA]
    })
    .compileComponents();

    fixture = TestBed.createComponent(MainComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it("should have correct numbers of <app-student-profile> child component(s)", () => {
    let childComponents = fixture.debugElement.queryAll(By.directive(StudentComponent));
    expect(childComponents.length).toEqual(studentIds.length)
  })

  it("should pass right argument to child components", () => {
    let childComponents = fixture.debugElement.queryAll(By.directive(StudentComponent));
    for (var i = 0; i < component.studentIds.length; i++) {
      let childComponent: StudentComponent = childComponents[i].componentInstance;
      expect(childComponent.studentId).toEqual(component.studentIds[i]);
    }
  })
});
On line 10, we mock the child component with MockComponent().

[NO_ERRORS_SCHEMA] is also not needed in the declaration like in other approaches without using ng-mocks library.

On line 25, we use fixture.debugElement.queryAll(By.directive(CHILD_COMPONENT_NAME)) to find all child components. Notice that with the library, we can find the child component by class name (type). Other approaches need to use a fake component class or querying the html element (using By.css() function).

Line 32, we get the child component object with .componentInstance. Then we will be able to access all its properties and methods. We can check the argument passed to its input property by directly inspecting its class property.


Lastly, we need to test the output property. It will relay an event then call this function on parent component:
displayAlert(message: string): void {
  console.log(message);
}
We can test this interaction with something like:
it("should be able to catch alert from child component", () => {
  const alertMessage: string = "test alert";
  spyOn(console, 'log');
  //spyOn(component, 'displayAlert');   // if we want to test the parent component function is called
  epProfileComponent = fixture.debugElement.query(By.directive(StudentEPProfileComponent)).componentInstance;
  epProfileComponent.alertEmitter.emit(alertMessage);
  //expect(component.displayAlert).toHaveBeenCalledWith(alertMessage);   // if we want to test the parent component function is called
  expect(console.log).toHaveBeenCalledWith(alertMessage);
})
Notice on line 6, we can call the emit() function and then on line 8, check that the parent's function we want to be called is called (in our example is console.log).

Friday, 11 August 2023

Angular Jasmine Unit Test - Faking Service and its Methods

On this post, we will try to create Jasmine unit tests to fake a service and its function that is used in an Angular component.
Used Angular version is 15 and Jasmine is 4.5.0.

Our component page:
export class HomeComponent implements OnInit {
  student?: StudentWithBasicProfile;

  constructor(
    private studentService: StudentService,
    private route: ActivatedRoute) { }

  ngOnInit(): void {
    let studentId = this.route.snapshot.params['id'];

    this.studentService.getStudentBasicProfile(studentId)
      .subscribe(student => {
          this.student = student;
      });
  }  
}
This component retrieves an id from query string then call a method of a service then display the result.

Our tests look like this:
describe('HomeComponent', () => {
  let component: HomeComponent;
  let fixture: ComponentFixture<HomeComponent>;
  let studentServiceSpy: jasmine.SpyObj<StudentService>;
  let response: StudentWithBasicProfile;
  let routeId: number = 123;

  beforeEach(async () => {
    studentServiceSpy = jasmine.createSpyObj('StudentService', ['getStudentBasicProfile']);
    response = {
      studentId: routeId,
      firstName: '',
      lastName: '',
      email: '',
      mobilePhone: ''
    };
    studentServiceSpy.getStudentBasicProfile.and.returnValue(of(response));

    await TestBed.configureTestingModule({
      declarations: [HomeComponent],
      providers: [
        { provide: StudentService, useValue: studentServiceSpy },
        { provide: ActivatedRoute, useValue: { snapshot: { params: { 'id': routeId } } } }  // this one is to fake "this.route.snapshot.params['id']" code
      ]
    })
    .compileComponents();

    fixture = TestBed.createComponent(HomeComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it("should call getStudentBasicProfile() with correct parameter and return data", () => {
    expect(studentServiceSpy.getStudentBasicProfile).toHaveBeenCalledTimes(1);
    expect(studentServiceSpy.getStudentBasicProfile).toHaveBeenCalledWith(routeId);
    expect(component.student).toEqual(response);
  });
});
On lines 4, 9, and 17, we set up a fake StudentService and its function 'getStudentBasicProfile' and set up a return value. Then on line 22, we use this fake service instead of the real service.

On line 23, we fake the query string value in route with "{ snapshot: { params: { 'id': [VALUE] } } }".

Friday, 24 March 2023

Using Entity Framework Core with Existing Database

We can use an existing database with Entity Framework Core (in this example is a MSSQL Database and EF Core 7). Say we need to create a new app that is accessing data from an established database. First we need to generate models and context class from our database. Then our codes can interact with these generated classes.

First of all, install Microsoft.EntityFrameworkCore, Microsoft.EntityFrameworkCore.Tools and Microsoft.EntityFrameworkCore.SqlServer packages to our project.

Then run Scaffold-DbContext command to generate models and context class. I would like to structure my projects like this:

To have this, I need to use some flags when running the command:
Scaffold-DbContext "Server=[SERVER_NAME];Database=[DATABASE_NAME];TrustServerCertificate=True;Trusted_Connection=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir "[MY_APP_DIRECTORY]\StudentApp\Model\Entity" -Namespace Model.Entity -Context StudentAppContext -ContextDir . -ContextNamespace Repository -Tables Student,School -Force
In my case, I use an Active Directory account that can access my database, so I use 'Server=[SERVER_NAME];Database=[DATABASE_NAME];TrustServerCertificate=True;Trusted_Connection=True'.
The flags used here:
- OutputDir - folder location where the generated model classes will be put
- Namespace - the namespace of the generated model classes
- Context - the name of the context file to be generated
- ContextDir - folder location where the context file will be put. I use '.' for current directory (I run the command from Repository project).
- ContextNamespace - namespace of the context file
- Tables - specify all table names in the database that we want to map
- Force - useful when we want to add new model(s) to be generated or simply to generate the whole thing again if we made mistake

Later when we want to add another model(s) from different table(s), we run the same command again with the new table name(s) added. For example if we want to add Teacher and Subject models:
Scaffold-DbContext "Server=[SERVER_NAME];Database=[DATABASE_NAME];TrustServerCertificate=True;Trusted_Connection=True" Microsoft.EntityFrameworkCore.SqlServer -OutputDir "[MY_WORK_DIRECTORY]\StudentApp\Model\Entity" -Namespace Model.Entity -Context StudentAppContext -ContextDir . -ContextNamespace Repository -Tables Student,School,Teacher,Subject -Force

Tuesday, 14 March 2023

.NET Core Built In Dependency Injection

.NET has built in tool for dependency injection. 'Microsoft.Extensions.DependencyInjection' library has common features needed for IoC that is usually good enough for most applications. However if you need more advanced features, consider using other DI tools.

To use, first install 'Microsoft.Extensions.DependencyInjection' package in our project.

To register the interfaces and objects, use either AddTransient(), AddScoped() or AddSingleton() methods. Their lifetimes are:
  • AddTransient - new instance is created each time requested
  • AddScoped - new instance is created per client request/session
  • AddSingleton - created once only through application life, subsequent requests will access the same instance
To understand the lifetimes better, please check this article .

Then we use GetService() method to get an instance.

An example of how to use the tool in a unit test:
[TestClass]
public class ExampleTest
{
    private readonly IStudentRepository studentRepository;

    public ExampleTest()
    {
        var services = new ServiceCollection();
        services.AddTransient<IStudentRepository, StudentRepository>();

        var serviceProvider = services.BuildServiceProvider();

        studentRepository = serviceProvider.GetService<IStudentRepository>();
		
		. . .
    }
}

To use it in different projects, we can create an extension method of IServiceCollection in the particular project. For example, in service project:
public static class IServiceCollectionExtension
{
	public static IServiceCollection AddServicesConfiguration(this IServiceCollection services)
	{
		services.AddTransient<IStudentService, StudentService>();
		return services;
	}
}
and in repository project:
public static class IServiceCollectionExtension
{
	public static IServiceCollection AddRepositoriesConfiguration(this IServiceCollection services)
	{
		services.AddTransient<IStudentRepository, StudentRepository>();
		return services;
	}
}
Some people like to do this approach but then a project that wants to use the extension method(s) needs to directly reference the other project(s). For example; a web project will need to reference service and repository projects.

Personally, I prefer to create a bootstrap project that contains all DI mappings and has references to all projects needed. Then a project just need to reference the bootstrap project.

In this example, the WebAPI project just need to reference Bootstrap project, not Service and Repository projects.

All DI configurations are in one place:
public static class BootstrapConfig
{
	public static IServiceCollection RegisterRepositories(this IServiceCollection services)
	{
		services.AddTransient<IStudentRepository, StudentRepository>();
		return services;
	}

	public static IServiceCollection RegisterServices(this IServiceCollection services)
	{
		services.AddTransient<IStudentService, StudentService>();
		return services;
	}
}

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.

Friday, 27 January 2023

User Authentication with AWS Cognito with Web API Backend and Frontend Web
Part 2 - Configure CORS in Web API backend

This is the second 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 - this post
  3. Authentication between frontend and Web API backend - next post

Since our frontend Angular app may be in different domain or at least port number in development (using localhost), we need to allow Cross-Origin Resource Sharing (CORS) in the backend Web API. There are two approaches for this, whether to allow only some methods by using a named policy or apply to all methods.

In ConfigureServices method inside Startup.cs file, to allow CORS for selected methods only, we put:
services.AddCors(options =>
{
	options.AddPolicy("EnableCORS", builder =>
			  builder.WithOrigins("http://localhost:4200", "https://localhost:4200")  // frontend domain(s)
				.AllowAnyHeader()
				.AllowAnyMethod()
				.AllowCredentials()
				//.WithMethods("OPTIONS", "GET")
	);
});

Then in Configure method, add:
. . .

app.UseCors("EnableCORS");

. . .
Important! Make sure to put app.UseCors() code in the right order as specify in the ASP.NET Core documentation


Then on the controller methods, we add EnableCors attribute, for example:
[EnableCors("EnableCORS")]
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
	. . .
}

If we want to enable CORS for all methods, we use AddDefaultPolicy() instead AddPolicy():
services.AddCors(options =>
{
	options.AddDefaultPolicy(builder =>
			  builder.WithOrigins("http://localhost:4200", "https://localhost:4200")  // frontend domain(s)
				.AllowAnyHeader()
				.AllowAnyMethod()
				.AllowCredentials()
				//.WithMethods("OPTIONS", "GET")
	);
});
Then in Configure method, just put:
app.UseCors();

On the next post, we will configure the Authentication part in the project.

Tuesday, 24 January 2023

User Authentication with AWS Cognito with Web API Backend and Frontend Web
Part 1 - Setting up AWS Cognito and backend Web API user functions

We will set 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 writing will be divided into three parts:
  1. Set up AWS Cognito and backend Web API user functions - this post
  2. Configure CORS in Web API backend - next post
  3. Authentication between frontend and Web API backend - next post

On this first series, we will set up AWS Cognito and ASP.NET Core Web API backend (.NET 5.0) with some user sign up an sign in functions.

1. First, create a new user pool in AWS Cognito then create a new App Client. Make sure 'Enable username password auth for admin APIs for authentication (ALLOW_ADMIN_USER_PASSWORD_AUTH)' is selected:


2. Create an ASP.NET Core Web API project. On Startup.cs file inside ConfigureServices method set options.Audience and options.Authority
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]";
	});

	. . .
}
App Client Id is shown on:

Then for the Authority field, we can get Region and User Pool Id from


3. In Configure(IApplicationBuilder app, IWebHostEnvironment env) method, add
app.UseAuthentication();
after app.UseRouting() is called.

4. Make sure your project has this AWSSDK.CognitoIdentityProvider package installed

5. Then we can add sign up feature in a controller
[HttpPost]
[Route("api/signup")]
public async Task<ActionResult<string>> SignUp(User user)
{
	var cognito = new AmazonCognitoIdentityProviderClient(_region);

	var request = new SignUpRequest
	{
		ClientId = _clientId,
		Password = user.Password,
		Username = user.Username
	};
    
	// Cognito email attribute
	var emailAttribute = new AttributeType
	{
		Name = "email",
		Value = user.Email
	};
	request.UserAttributes.Add(emailAttribute);

	var response = await cognito.SignUpAsync(request);

	return Ok(response);
}

6. When a user sign up with the method above, its Account Status is "Unconfirmed" and Email Verified is "false". A confirmation email will be sent with a code. We need to add another function to handle this.
[HttpPost]
[Route("api/confirmSignUp")]
public async Task<ActionResult<string>> ConfirmSignUp(string username, string confirmationCode)
{
	var cognito = new AmazonCognitoIdentityProviderClient(_region);

	var request = new ConfirmSignUpRequest
	{
		ClientId = _clientId,
		Username = username,
		ConfirmationCode = confirmationCode
	};

	var response = await cognito.ConfirmSignUpAsync(request); // after calling this method, user's Account Status will become 'Confirmed' and Email Verified become 'true'

	return Ok(response);
}

7. Then the sign in function. This will return an Access Token if successful.
[HttpPost]
[Route("api/signin")]
public async Task<ActionResult<string>> SignIn([FromBody] User user)
{
	var cognito = new AmazonCognitoIdentityProviderClient(_region);

	var request = new AdminInitiateAuthRequest
	{
		UserPoolId = _userPoolId, // User Pool Id
		ClientId = _clientId, // App Client Id
		AuthFlow = AuthFlowType.ADMIN_USER_PASSWORD_AUTH
	};

	request.AuthParameters.Add("USERNAME", user.Username);
	request.AuthParameters.Add("PASSWORD", user.Password);

	var response = await cognito.AdminInitiateAuthAsync(request);

	return Json(response.AuthenticationResult.AccessToken);
}

Additional resend confirmation code and find user functions:
[HttpPost]
[Route("api/resendConfirmationCode")]
public async Task<ActionResult<string>> ResendConfirmationCode(string username)
{
	var cognito = new AmazonCognitoIdentityProviderClient(_region);

	var request = new ResendConfirmationCodeRequest
	{
		ClientId = _clientId,
		Username = username
	};

	var response = await cognito.ResendConfirmationCodeAsync(request);

	return Ok(response);
}


[HttpPost]
[Route("api/findUser")]
public async Task<ActionResult<string>> FindUser(string username)
{
	var cognito = new AmazonCognitoIdentityProviderClient(_region);

	var request = new AdminGetUserRequest
	{
		UserPoolId = _userPoolId,
		Username = username
	};
	var response = await cognito.AdminGetUserAsync(request);

	return Ok(response);    
}

On the next post, we will set up CORS (Cross-Origin Resource Sharing) in the project.