Mastering Angular 16 Authentication: Build a Secure and Seamless Authentication System for Your Applications
This tutorial covers:
Introduction
Building a robust and scalable web applications is essential for businesses to thrive. In this article, we'll explore the process of creating a web application using Angular, focusing on how to connect the Angular frontend with a backend API. We'll cover the steps involved, from setting up the development environment to implementing the frontend components, services, interceptors and Guards.
Additionally, we'll incorporate the use of JSON Web Tokens (JWT) for authentication and authorization. We'll explore the architecture of JWT tokens and demonstrate how to manipulate the token for authentication purposes. We'll also implement interceptors in Angular to send the token with each request and handle responses effectively.
Throughout this tutorial, we'll not only focus on connecting the Angular frontend with a backend API but also showcase the power of Angular Materials, Guards in building a secure,-friendly, and highly interactive web application. To provide the necessary backend functionality, we'll refer to an existing Spring Boot project on my GitHub account. You can find the project at this Link. We'll demonstrate how to interact with this backend API, retrieve data and handle form submissions using the FormBuilder.
By the end of this tutorial, you'll have a solid understanding of how to connect an Angular project with a backend API, utilize Angular Materials for UI enhancements, implement Guards and interceptors for authentication, and harness the power of the FormBuilder for efficient form handling. So, let's dive in!
Introduction to Angular and JWT Authentication
Angular is a popular front-end framework, provides developers with a powerful toolset to create dynamic and responsive web applications. When it comes to implementing user authentication and ensuring secure communication between the front-end and back-end, JSON Web Tokens (JWT) offer an effective solution.
Running the Angular Project and Interconnecting Components
To understand how the Angular project functions and how its different parts interact, let's examine the project's architecture. The core component that serves as the starting point of our application is the `app.component`. From here, we establish connections with other components, creating a seamless flow of data and functionality.
Within our project, we have four key components: `RegisterComponent`, `LoginComponent`, `UserComponent`, and `AdminComponent`. Each component serves a distinct purpose in our application, and they are closely tied to specific services and functionalities.
The `RegisterComponent` and `LoginComponent` are connected to the `AuthService`. This service plays a vital role in user authentication by allowing users to either log in or create a new account. Through the `AuthService`, we can securely handle user credentials and manage the authentication process.
On the other hand, the `UserComponent` and `AdminComponent` rely on the `UserService`. This service enables us to perform essential CRUD (Create, Read, Update, Delete) operations on user data. With the `UserService`, we can retrieve user information, update profiles, and manage user-related tasks efficiently.
To ensure the security and integrity of our application, we employ the `AuthInterceptor`. This interceptor acts as a gateway for all outgoing requests from our application. Whenever a request is made, the `AuthInterceptor` intercepts it and injects the authentication token into the request headers. This token enables the server to authenticate and authorize the requests, maintaining a secure connection between the Angular frontend and the backend API.
The `AuthInterceptor` retrieves the authentication token from the session storage. Session storage is a web storage mechanism that allows us to store data securely on the client side. In our case, we store the authentication token in the session storage after a successful login. The `AuthInterceptor` accesses this token from the session storage and adds it to the request headers before forwarding the request to the server.
This approach ensures that the token is automatically included with each request, eliminating the need for manual token handling in every service or component. It provides a seamless way to authenticate and authorize requests without compromising security.
Here's an image that visually represents the interconnections between the components, services, and the `AuthInterceptor`:
This visual representation provides a clear overview of how the components, services, and interceptors collaborate within our Angular project. It showcases the seamless flow of data and the role of the `AuthInterceptor` in handling authentication and securing our application by retrieving the authentication token from session storage.
Understanding this architecture and the interconnectedness of the components, services, and interceptors will provide a solid foundation as we proceed with implementing and manipulating JWT tokens, as well as incorporating interceptors for token management in subsequent sections.
Understanding the principles of token-based authentication:
Token-based authentication has gained popularity due to its stateless nature and improved security. Tokens, such as JSON Web Tokens (JWTs), play a crucial role in this process. While the generation of tokens typically occurs on the server side, their handling involves both server-side and client-side components.
In the context of this article, we emphasize the client-side handling of tokens. Once generated on the server side, tokens are securely transmitted to the client, where they are stored and later included in subsequent requests for authentication. This approach allows for scalability, easy integration with APIs, and enhanced security by eliminating the need for server-side session storage.
It's important to note that the initial generation of tokens takes place on the server side, ensuring a secure and controlled process.
An overview of JSON Web Tokens (JWT):
JSON Web Tokens (JWT) are a compact and self-contained means of transmitting information securely between parties as a JSON object. JWTs consist of three parts: a header, a payload, and a signature. The header specifies the algorithm used for signing the token, the payload contains the claims or information about the user, and the signature ensures the integrity and authenticity of the token. JWTs are widely used for authentication and authorization purposes in web applications.
Now that we have a solid understanding of Angular's architecture and the fundamentals of JWT authentication,let's dive into exploring how to interface with the backend API, handle HTTP requests and responses.
Understanding Backend Models and APIs for Secure Authentication
In the world of web application development, the backend plays a crucial role in managing data, processing requests, and implementing business logic. In this section, we will explore the models and APIs that constitute the backend of our application, focusing specifically on the components related to secure authentication.
Backend Models: Class Diagrams
+-------------------------------------+
| UserEntity |
+-------------------------------------+
| - id: long |
| - userId: String |
| - firstname: String |
| - lastname: String |
| - email: String |
| - admin: Boolean |
| - addresses: List<AddressEntity> |
+-------------------------------------+
+-------------------------------------+
| AddressEntity |
+-------------------------------------+
| - id: long |
| - addressId: String |
| - city: String |
| - country: String |
| - street: String |
| - postal: String |
| - type: String |
+-------------------------------------+
Exploring API Testing with Postman
We will leverage Postman, a powerful API testing tool, to verify the functionality of our backend APIs.
1. Login Successful: Obtaining the Token
To begin, we will simulate a successful login scenario. In the first image, we can see the Postman request configured to send login credentials to the backend API endpoint. Once the request is executed, we receive a response containing the authentication token. This token serves as proof of successful authentication and will be used to access protected resources.
In the image below, we illustrate the structure of the authentication token, which consists of three parts: header, payload, and signature.
You can see your own token header, payload, and signature in the following Link.
2. Retrieving Data with the Token
Next, we will demonstrate how to use the obtained token to retrieve data from the backend. In the second image, we configure a new Postman request, including the token in the request headers. This request is sent to the appropriate API endpoint responsible for retrieving data.
Upon executing the request, we can observe the response, which includes the desired data. This confirms that our authentication system is functioning correctly and that the token is being recognized and authorized by the backend.
3. Failing to Retrieve Data Without a Token
Finally, we will test the scenario where a request is made to retrieve data without providing a token. In the third image, we can see a Postman request configured to access a protected API endpoint without including the required token in the headers.
When executing this request, we receive an error response (403 Forbidden) indicating the absence of a valid token. This failure scenario serves as a crucial security measure, preventing unauthorized access to sensitive data.
By exploring the backend models and APIs along with their testing using Postman, we have gained a solid understanding of the authentication system. Now, equipped with this knowledge, we are ready to dive into creating our Angular project and developing a comprehensive authentication system. Let's embark on this journey and build a robust and secure authentication system for our application.
Creating a sample Angular application
Step 1 : Installing the Angular CLI
The Angular CLI is a command-line interface tool that you use to initialize, develop, scaffold, and maintain Angular applications directly from a command shell.
In order to create an Angular project, it is necessary to install the Angular CLI on the development system. This requires the installation of Node.js.
Follow the Node.js?installation URL?on your developer environment.
Install the CLI using the npm package manager on a terminal:
npm install -g @angular/cli
Step 2 : Creating an Angular Project
To get started, let's create a fresh Angular project using Angular CLI by typing the command below (on a terminal) with the project name “AuthAngular”.
ng new AuthAngular
Hit?y?to add Angular Routing to your project
Hit Enter to choose stylesheet format css (Cascading Style Sheets)
Once the Angular project is setup then get into the project folder by using the following command.
cd AuthAngular
Step 3 : Open Angular Project
Your basic Angular app is almost ready just hit the below command in terminal.
ng serve --open
Now your browser should be opened and you'll see this
Building the Foundation of the project
Step 1 : Adding MDB Bootstrap to Your Angular Application
To incorporate MDB Bootstrap into your Angular application, follow these steps:
<!doctype html>
<html lang="en">
<head>
? <meta charset="utf-8">
? <title>AuthAngular</title>
? <base href="/">
? <meta name="viewport" content="width=device-width, initial-scale=1">
? <link rel="icon" type="image/x-icon" href="favicon.ico">
? <link rel="preconnect" >
? <link rel="stylesheet">
? <link rel="stylesheet">
?
<!-- Font Awesome -->
? <link
?
? rel="stylesheet"
? />
? <!-- Google Fonts -->
? <link
?
? rel="stylesheet"
? />
? <!-- MDB -->
? <link
?
? rel="stylesheet"
? />
</head>
<body class="mat-typography">
? <app-root></app-root>
</body>
</html>
Note: The URL provided in the example above is for version 6.4.0 of MDB Bootstrap. Make sure to use the appropriate version based on your requirements.
By adding the MDB Bootstrap <link> tag to the index.html file, you are referencing the MDB Bootstrap CSS file hosted on a CDN. This allows your Angular application to access the MDB Bootstrap styles and apply them to the UI components.
Now, when you run your Angular application, it will include the MDB Bootstrap styles, enabling you to use the MDB Bootstrap components and styles in your application.
Remember to refer to the MDB Bootstrap documentation for guidance on utilizing the available components and customizing them to fit your application's specific needs.
Step 2: Setting up Angular Material
Installing Angular Material: Open your terminal or command prompt and navigate to your Angular project's root directory. Run the following command to install Angular Material and its required dependencies:
ng add @angular/material
This command will automatically install Angular Material and prompt you to choose a prebuilt theme.
Import Angular Material Modules: When working with Angular Material, it is a good practice to create a separate file called material.module.ts to centralize the import of all Angular Material modules. This approach helps to keep the app.module.ts file organized and avoids making it overly long.
Here are the reasons why creating a separate material.module.ts file is beneficial:
To use this approach, follow these steps:
import {NgModule} from '@angular/core';
import {A11yModule} from '@angular/cdk/a11y';
import {CdkAccordionModule} from '@angular/cdk/accordion';
import {ClipboardModule} from '@angular/cdk/clipboard';
import {DragDropModule} from '@angular/cdk/drag-drop';
import {PortalModule} from '@angular/cdk/portal';
import {ScrollingModule} from '@angular/cdk/scrolling';
import {CdkStepperModule} from '@angular/cdk/stepper';
import {CdkTableModule} from '@angular/cdk/table';
import {CdkTreeModule} from '@angular/cdk/tree';
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatBadgeModule} from '@angular/material/badge';
import {MatBottomSheetModule} from '@angular/material/bottom-sheet';
import {MatButtonModule} from '@angular/material/button';
import {MatButtonToggleModule} from '@angular/material/button-toggle';
import {MatCardModule} from '@angular/material/card';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatChipsModule} from '@angular/material/chips';
import {MatStepperModule} from '@angular/material/stepper';
import {MatDatepickerModule} from '@angular/material/datepicker';
import {MatDialogModule} from '@angular/material/dialog';
import {MatDividerModule} from '@angular/material/divider';
import {MatExpansionModule} from '@angular/material/expansion';
import {MatGridListModule} from '@angular/material/grid-list';
import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {MatListModule} from '@angular/material/list';
import {MatMenuModule} from '@angular/material/menu';
import {MatNativeDateModule, MatRippleModule} from '@angular/material/core';
import {MatPaginatorModule} from '@angular/material/paginator';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {MatRadioModule} from '@angular/material/radio';
import {MatSelectModule} from '@angular/material/select';
import {MatSidenavModule} from '@angular/material/sidenav';
import {MatSliderModule} from '@angular/material/slider';
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatSortModule} from '@angular/material/sort';
import {MatTableModule} from '@angular/material/table';
import {MatTabsModule} from '@angular/material/tabs';
import {MatToolbarModule} from '@angular/material/toolbar';
import {MatTooltipModule} from '@angular/material/tooltip';
import {MatTreeModule} from '@angular/material/tree';
import {OverlayModule} from '@angular/cdk/overlay';
import {CdkMenuModule} from '@angular/cdk/menu';
import {DialogModule} from '@angular/cdk/dialog';
@NgModule({
? exports: [
? ? A11yModule,
? ? CdkAccordionModule,
? ? ClipboardModule,
? ? CdkMenuModule,
? ? CdkStepperModule,
? ? CdkTableModule,
? ? CdkTreeModule,
? ? DragDropModule,
? ? MatAutocompleteModule,
? ? MatBadgeModule,
? ? MatBottomSheetModule,
? ? MatButtonModule,
? ? MatButtonToggleModule,
? ? MatCardModule,
? ? MatCheckboxModule,
? ? MatChipsModule,
? ? MatStepperModule,
? ? MatDatepickerModule,
? ? MatDialogModule,
? ? MatDividerModule,
? ? MatExpansionModule,
? ? MatGridListModule,
? ? MatIconModule,
? ? MatInputModule,
? ? MatListModule,
? ? MatMenuModule,
? ? MatNativeDateModule,
? ? MatPaginatorModule,
? ? MatProgressBarModule,
? ? MatProgressSpinnerModule,
? ? MatRadioModule,
? ? MatRippleModule,
? ? MatSelectModule,
? ? MatSidenavModule,
? ? MatSliderModule,
? ? MatSlideToggleModule,
? ? MatSnackBarModule,
? ? MatSortModule,
? ? MatTableModule,
? ? MatTabsModule,
? ? MatToolbarModule,
? ? MatTooltipModule,
? ? MatTreeModule,
? ? OverlayModule,
? ? PortalModule,
? ? ScrollingModule,
? ? DialogModule,
? ]
})
export class MaterialModule {}
3. Export the MaterialModule: Ensure that you export the MaterialModule class so that it can be imported in other modules.
4. Import MaterialModule in app.module.ts: In your app.module.ts file, import the MaterialModule by adding the following import statement:
import { MaterialModule } from './material.module';
5. Include MaterialModule in the imports array: Add MaterialModule to the imports array of the @NgModule decorator in the app.module.ts file:
@NgModule({
? imports: [
? ? // Other module imports
? ? MaterialModule
? ],
? // Other module configurations...
})
export class AppModule { }
By following this approach, you can keep your app.module.ts file clean and focused on application-specific configurations, while all Angular Material-related imports are centralized in the material.module.ts file. This improves code organization, reusability, and maintainability.
Step 3: Importing Reactive Forms, HttpClientModule
To work with reactive forms and make HTTP requests in your Angular project, you need to import the following modules: `ReactiveFormsModule` and `HttpClientModule`.
In your Angular project, open the `app.module.ts` file and add the following import statements at the top:
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
Then, include both modules in the `imports` array of the `@NgModule` decorator:
@NgModule({
? imports: [
? ? // Other module imports
? ? ReactiveFormsModule,
? ? HttpClientModule
? ],
? // Other module configurations...
})
export class AppModule { }
The `ReactiveFormsModule` module is necessary to work with reactive forms, while the `HttpClientModule` is required to make HTTP requests in Angular using the HttpClient service.
Step 4: Importing @auth0/angular-jwt:
To handle JWT authentication, you need to install the @auth0/angular-jwt library first. Run the following command in your project's root directory to install it:
npm install @auth0/angular-jwt
and then run install the Jwt Decoder : jwt-decode is a small browser library that helps decoding JWTs token which are Base64Url encoded.
npm install jwt-decode
Once the library is installed, import it in your Angular project. In the app.module.ts file, add the following import statement:
import { JwtModule } from '@auth0/angular-jwt';
Then, include JwtModule.forRoot() in the imports array of the @NgModule decorator:
export function tokenGetter() {
? return sessionStorage.getItem("TOKEN_KEY");
}
@NgModule({
? declarations: [
? ? AppComponent
? ],
? imports: [
? ? // Other module imports
? ? JwtModule.forRoot({
? ? ? config: {
? ? ? ? tokenGetter: tokenGetter,
? ? ? ? allowedDomains: ["localhost:8080"], //add all your allowed domain
? ? ? ? disallowedRoutes: [],
? ? ? },
? ? }),
? ],
Note: The forRoot() method allows you to provide configuration options for the JwtModule. Refer to the @auth0/angular-jwt documentation for more details on how to configure and use this module as per request.
Building Angular Models, Services and Interceptor
Now that we have set up our Angular project and imported the necessary modules, let's move on to building the essential components of our application. In this section, we will focus on creating models, services and interceptor that will be crucial for implementing our authentication flow and interacting with the backend API.
Step 1 : Manage Angular Models
In this step, we will create and manage Angular models, which will represent the data structures used in our application. Models define the shape and properties of our data objects, providing a structured and type-safe way to handle data.
To create Angular models type the command below (on a terminal)
ng generate class models/Address
In the above command,?models?represents the folder where you want to generate the model, and?Address is the name of the model. You can modify these values according to your project structure and desired model name.
After setting up the necessary import , we can now proceed to add the necessary variables to our class.
export class Address{
? addressId: string;
? city: string;
? country: string;
? street: string;
? postal: string;
? type: string;
? constructor() {
? ? this.addressId = "";
? ? this.city = "";
? ? this.country = "";
? ? this.street = "";
? ? this.postal = "";
? ? this.type = "";
? }
}
Next the User Model ,to create User model type the command below (on a terminal)
ng generate class models/User
We can now proceed to add the necessary variables to our class.
import { Address } from "./adress";
export class User {
? userId: string;
? firstname: string;
? lastname: string;
? email: string;
? admin: boolean;
? addresses: Address[];
? constructor() {
? ? this.userId = "";
? ? this.firstname = "";
? ? this.lastname = "";
? ? this.email = "";
? ? this.admin = false;
? ? this.addresses = [];
? }
}
Step 2: Manage Angular Services
Now that we have created our models, it's time to manage our Angular services. Services in Angular provide a way to encapsulate and share data, logic, and functionality across different components.
To create a new service, run the following commands in your Angular project's root directory:
ng generate service Services/user
ng generate service Services/Address
ng generate service Services/data
ng generate service Services/auth
Inside the generated service file, you can define variables, methods, and logic specific to your service's functionality.
Let's start with editing our?DataService.ts
import { Inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
? providedIn: 'root'
})
export class DataService {
? ?// Injected ApiUrl in constructor to Get it form ather Service
? ?constructor(@Inject(String) private APIUrl: string,private http: HttpClient) { }
? // Get Method
? getAll(): Observable<any> {
? ? return this.http.get<any>(this.APIUrl);
? }
? // Get with id Method
? get(id: any): Observable<any> {
? ? return this.http.get(`${this.APIUrl}/${id}`);
? }
? // Update Method
? Update(data: any): Observable<any> {
? ? return this.http.put(`${this.APIUrl}`, data);
? }
? // Create Method
? Create(data: any): Observable<any> {
? ? return this.http.post(this.APIUrl, data);
? }
? // Delete Method
? Delete(id: any): Observable<any> {
? ? return this.http.delete(`${this.APIUrl}/${id}`);
? }
}
After creating our?DataService, we can move on to other services in our application. These services will primarily depend on the?DataService?to interact with the API and perform various operations. The advantage of this approach is that we only need to inject the API URL into the?DataService, which will provide all the necessary functions.
Let's take an example of a?UserService?that handles user-related operations. In the?UserService, we can inject the?DataService?and configure it with the API URL:
import { Injectable } from '@angular/core';
import { DataService } from './data.service';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
const ?APIUrlUser ="https://localhost:8080/api/users";
const ?APIUrlAuth =" https://localhost:8080/api/users/login";
@Injectable({
? providedIn: 'root'
})
export class UserService ?extends DataService{
? constructor(http:HttpClient,private httpPrivate : HttpClient){
? ? super(APIUrlUser,http);
? }
? // Login Method
? signIn(data :{email : string,password : string}): Observable<any>{
? ? console.log(data)
? ? return this.httpPrivate.post(APIUrlAuth, data);
? }
}
signIn(data: { email: string, password: string }): Performs a HTTP POST request to sign in the user using the provided email and password.
And we do the same with?AddressService.ts
领英推荐
import { Injectable } from '@angular/core';
import { DataService } from './data.service';
import { HttpClient } from '@angular/common/http';
const ?APIUrlAddress="https://localhost:8080/api/addresses";
@Injectable({
? providedIn: 'root'
})
export class AddressService extends DataService{
? constructor(http:HttpClient){
? ? super(APIUrlAddress,http);
? }
}
The AuthService is responsible for managing the JWT token and user information in the session storage. It provides the following functionality:
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import jwt_decode from 'jwt-decode';
import { Observable } from 'rxjs';
const TOKEN_KEY = 'TOKEN_KEY';
@Injectable({
? providedIn: 'root'
})
export class AuthService {
? constructor(private router : Router) {}
? public saveToken(token : string): void {
? ? window.sessionStorage.removeItem(TOKEN_KEY);
? ? window.sessionStorage.setItem(TOKEN_KEY, token);
? }
? public getToken(): string | null {
? ? return window.sessionStorage.getItem(TOKEN_KEY) !== null ? window.sessionStorage.getItem(TOKEN_KEY) : null;
? }
? public getUser():string | null{
? ? const jwtToken = this.getToken();
? ? ? const decodedToken: any = this.getToken() != null ? jwt_decode(jwtToken as string) : null;
? ? ? const userId = decodedToken != null ? decodedToken?.sub : null;
? ? return userId;
? }
? public getUserId():string | null{
? ? const jwtToken = this.getToken();
? ? ? const decodedToken: any = this.getToken() != null ? jwt_decode(jwtToken as string) : null;
? ? ? const userId = decodedToken != null ? decodedToken?.id : null;
? ? return userId;
? }
? public getRole(){
? ? const jwtToken = this.getToken();
? ? ? const decodedToken: any = this.getToken() != null ? jwt_decode(jwtToken as string) : null;
? ? ? const userRole = decodedToken != null ? decodedToken?.Role : null;
? ? return userRole;
}
? ? signOut(): void {
? ? ? window.sessionStorage.clear();
? ? ? this.router.navigate(['/Home'])
? ? ? .then(() => {
? ? ? ? window.location.reload();
? ? ? });
? ?}
? }
By utilizing the AuthService , you can securely store and retrieve the JWT token, as well as access user-related information for authentication and authorization purposes.
Step 3: Manage Angular Interceptor
In order to manipulate the JWT token and send it with each request, we will utilize Angular interceptors. Interceptors provide a convenient way to intercept HTTP requests and responses, allowing us to modify or handle them as needed. In this step, we will focus on managing the Angular interceptor to handle JWT token manipulation and inclusion with each request.
ng generate interceptor Interceptor/Auth
Now, let's customize our Auth Interceptor by navigating to src/app/interceptor/auth.interceptor.ts and adding the following code:
import { Injectable } from '@angular/core';
import {
? HttpRequest,
? HttpHandler,
? HttpEvent,
? HttpInterceptor,
? HTTP_INTERCEPTORS,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService } from '../Services/auth.service';
const TOKEN_HEADER_KEY = 'Authorization'; // Header key for JWT token in the request
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
? constructor(private token: AuthService) {}
? intercept(
? ? req: HttpRequest<any>,
? ? next: HttpHandler
? ): Observable<HttpEvent<any>> {
? ? let authReq = req;
? ? const token = this.token.getToken();
? ? if (token != null) {
? ? // Add JWT token to the request header
? ? authReq = req.clone({
? ? ? ? headers: req.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + token),
? ? ? });
? ? // for Node.js Express back-end
? ? //authReq = req.clone({ headers: req.headers.set(TOKEN_HEADER_KEY, token) });
? ? }
? ? return next.handle(authReq);
? }
}
export const authInterceptorProviders = [
? { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
];
The AuthInterceptor is an HTTP interceptor that intercepts outgoing HTTP requests and adds the JWT token to the request headers. This ensures that the backend server can authenticate and authorize the user based on the provided token.
In the intercept method, the interceptor checks if a token exists. If a token is found, it adds it to the request headers using the TOKEN_HEADER_KEY and the 'Bearer' authentication scheme.
The authInterceptorProviders array is used to provide the AuthInterceptor as a multi-provider for the HTTP_INTERCEPTORS token. This allows the interceptor to be injected and used globally throughout the application.
By using this interceptor, all outgoing HTTP requests will automatically include the JWT token in the Authorization header, ensuring secure communication with the backend server.
Now let's import it in app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from './material.module';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { JwtModule } from '@auth0/angular-jwt';
import { authInterceptorProviders } from './Interceptor/auth.interceptor';
import { DataService } from './Services/data.service';
import { UserService } from './Services/user.service';
import { AddressService } from './Services/address.service';
import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar';
export function tokenGetter() {
? return sessionStorage.getItem("TOKEN_KEY");
}
@NgModule({
? declarations: [
? ? AppComponent,
? ],
? imports: [
? ? BrowserModule,
? ? AppRoutingModule,
? ? BrowserAnimationsModule,
? ? MaterialModule,
? ? ReactiveFormsModule,
? ? HttpClientModule,
? ? JwtModule.forRoot({
? ? ? config: {
? ? ? ? tokenGetter: tokenGetter,
? ? ? ? allowedDomains: ["localhost:8080"],
? ? ? ? disallowedRoutes: [],
? ? ? },
? ? }),
? ],
? providers: [
? ? authInterceptorProviders,
? ? DataService,
? ? UserService,
? ? AddressService,
AuthService,
? ? {provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: {duration: 3500}},
? ],
? bootstrap: [AppComponent],
? schemas: [
? ? CUSTOM_ELEMENTS_SCHEMA
? ]
})
export class AppModule { }
;
In the provided AppModule code, you'll notice the inclusion of CUSTOM_ELEMENTS_SCHEMA and several additional providers. Here's an explanation for each of them:
Including these services in the providers array makes them accessible to other components and services within your application.
These additional elements and providers in the AppModule reflect the necessary dependencies and configurations for your application to function properly.
Creating Components and Designing the Application
In this section, we will explore how to create components in Angular and discuss best practices for designing the application's user interface. We will cover topics such as component structure, styling with CSS or Angular Material, and implementing responsive design principles. By the end, you will have a solid understanding of how to create reusable components and design a cohesive and visually pleasing application. Let's get started!
Step 1 :Creating Necessary Components
To build a functional and interactive application, we need to create several components that will handle different parts of the user interface and encapsulate their behavior. In this section, we will go through the process of creating the following components:
ng g component Components/admin
ng g component Components/user
ng g component Components/login
ng g component Components/register
ng g component Components/home
Note: If you encounter any issues while creating the components using the Angular CLI, such as the CLI preventing the creation of a new component, you can try using the `--skip-import` flag. This flag will skip the automatic import of the component in the `app.module.ts` file. In such cases, you will need to manually import the components in the `app.module.ts` file to ensure they are properly registered and accessible throughout the application.
Step 2 : Creating Guards
Once we have our components ready, the next step is to define the routes in our Angular application. Routes allow us to navigate between different components based on the URL. Additionally, we can add guards to restrict access to certain routes based on user authentication.
In our Angular application, we want to implement two guards to secure both the outer and inner pages. The outer pages are accessible to any user, whether authenticated or not, while the inner pages require authentication.
To create a guard, we can use the Angular CLI command :
ng generate guard Guards/auth
ng generate guard Guards/secure-inner-pages
This will generate a new guards files with the specified name in the guards folder.
Once the guard is generated, we can implement the necessary logic inside its corresponding class. Some common tasks performed by guards include checking if the user is authenticated, verifying user roles or permissions, and redirecting to specific routes based on the authorization status.
Angular provides different types of guards that we can use based on our requirements. the one we will use it :
CanActivate: This guard determines if a user can access a particular route. We can implement custom logic to check if the user is authenticated or has the necessary permissions.
You can also check all types of guards and routes in the following Link.
To edit the Auth Guard, open the auth.guard.ts file and make the necessary modifications. Here is a sample code structure for an Auth Guard:
import { Injectable } from '@angular/core';
import {
? ActivatedRouteSnapshot,
? CanActivate,
? Router,
? RouterStateSnapshot,
? UrlTree,
} from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '../Services/auth.service';
import { MatSnackBar } from '@angular/material/snack-bar';
import { JwtHelperService } from '@auth0/angular-jwt';
import jwt_decode from 'jwt-decode';
@Injectable({
? providedIn: 'root',
})
export class AuthGuard implements CanActivate {
? constructor(
? ? public authService: AuthService,
? ? public router: Router,
? ? private _snackBar: MatSnackBar,
? ? private jwtHelper: JwtHelperService
? ) {}
? canActivate(
? ? route: ActivatedRouteSnapshot,
? ? state: RouterStateSnapshot
? ): Observable<boolean> | Promise<boolean> | boolean {
? ? const jwtToken = this.authService.getToken();
? ? const decodedToken: any =
? ? ? this.authService.getToken() != null
? ? ? ? ? jwt_decode(jwtToken as string)
? ? ? ? : null;
? ? const userRole = decodedToken != null ? decodedToken.Role : null;
? ? if (!jwtToken || this.jwtHelper.isTokenExpired(jwtToken)) {
? ? ? // Check if the token is missing or expired
? ? ? if (this.jwtHelper.isTokenExpired(this.authService.getToken())) {
? ? ? ? this._snackBar.open(
? ? ? ? ? 'Your session has expired. Please log in again.',
? ? ? ? ? '?'
? ? ? ? );
? ? ? ? this.authService.signOut();
? ? ? ? this.router.navigate(['/SignIn'], {
? ? ? ? ? queryParams: { returnUrl: state.url },
? ? ? ? });
? ? ? } else {
? ? ? ? this._snackBar.open('Access Denied!', '?');
? ? ? ? this.router.navigate(['/SignIn'], {
? ? ? ? ? queryParams: { returnUrl: state.url },
? ? ? ? });
? ? ? }
? ? } else {
? ? ? if (route.data['role'] && route.data['role'].indexOf(userRole) === -1) {
? ? ? ? // Check if the user's role is not granted access
? ? ? ? this._snackBar.open('Access Denied! Role Not Granted.', '?');
? ? ? ? this.router.navigate(['/Home'], {
? ? ? ? ? queryParams: { returnUrl: state.url },
? ? ? ? });
? ? ? ? return false;
? ? ? } else {
? ? ? ? return true;
? ? ? }
? ? }
? ? return true;
? }
}
After editing the AuthGuard, let's move on to creating the SecureInnerPagesGuard to secure our inner pages.
import { Injectable } from '@angular/core';
import {
? ActivatedRouteSnapshot,
? CanActivate,
? Router,
? RouterStateSnapshot,
} from '@angular/router';
import { Observable } from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
import { JwtHelperService } from '@auth0/angular-jwt';
import { AuthService } from '../Services/auth.service';
@Injectable({
? providedIn: 'root',
})
export class SecureInnerPagesGuard implements CanActivate {
? constructor(
? ? public authService: AuthService,
? ? public router: Router,
? ? private _snackBar: MatSnackBar,
? ? private jwtHelper: JwtHelperService
? ) {}
? canActivate(
? ? next: ActivatedRouteSnapshot,
? ? state: RouterStateSnapshot
? ): Observable<boolean> | Promise<boolean> | boolean {
? ? // Check if the user is already logged in and the token is not expired
? ? if (
? ? ? this.authService.getToken() &&
? ? ? !this.jwtHelper.isTokenExpired(this.authService.getToken())
? ? ) {
? ? ? this._snackBar.open('Access Denied, You are already logged in!', '?');
? ? ? this.router.navigate(['/Home'], {
? ? ? ? queryParams: { returnUrl: state.url },
? ? ? });
? ? }
? ? return true;
? }
}
Step 3 :Creating Routes
To create routes in your Angular application, follow these steps:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './Components/login/login.component';
import { SecureInnerPagesGuard } from './Guards/secure-inner-pages.guard';
import { RegisterComponent } from './Components/register/register.component';
import { HomeComponent } from './Components/home/home.component';
import { UserComponent } from './Components/user/user.component';
import { AdminComponent } from './Components/admin/admin.component';
import { AuthGuard } from './Guards/auth.guard';
const routes: Routes = [
? {
? ? path: 'SignIn',
? ? component: LoginComponent,
? ? canActivate: [SecureInnerPagesGuard],
? },
? {
? ? path: 'SignUp',
? ? component: RegisterComponent,
? ? canActivate: [SecureInnerPagesGuard],
? },
? {
? ? path: 'User',
? ? component: UserComponent,
? ? canActivate: [AuthGuard],
? ? data: {
? ? ? role: ['User','Admin'],
? ? },
? },
? {
? ? path: 'Admin',
? ? component: AdminComponent,
? ? canActivate: [AuthGuard],
? ? data: {
? ? ? role: ['Admin'],
? ? },
? },
? { path: 'Home', component: HomeComponent },
? { path: '', redirectTo: '/Home', pathMatch: 'full' }, // redirect to Home component on root path
];
@NgModule({
? imports: [RouterModule.forRoot(routes)],
? exports: [RouterModule],
})
export class AppRoutingModule {}
There are routes defined for the SignIn and SignUp paths, which are associated with the LoginComponent and RegisterComponent respectively. These paths are guarded by the SecureInnerPagesGuard to prevent accessing these pages when the user is already authenticated.
There are also routes for User and Admin paths, associated with the UserComponent and AdminComponent respectively. These paths are guarded by the AuthGuard and have additional data specifying the required roles for access.
The Home path is associated with the HomeComponent, and the empty path '' is redirected to the Home path.
Step 4 : Designing our application
After building the foundational elements of our application, it's time to shift our focus to designing the user interface. A well-designed application not only enhances the user experience but also adds a professional and polished look to your project.
To begin the design phase, we will start by enhancing the app.component to include a navigation bar. The navigation bar will serve as a consistent element throughout our application, providing easy navigation between different sections or pages.
In the app.component.html template file, we can start by adding a <header> element to encapsulate the navigation bar. Within the <header>, we can include a <nav> element to structure our navigation links.
Next, we can add the desired navigation links as <a> tags within the <nav> element. These links can be styled using CSS to match the design requirements of our application.
<header class="container header">
? <!-- ==== NAVBAR ==== -->
? <nav class="nav">
? ? <div class="logo">
? ? ? <h2 routerLink="/Home" style="cursor: pointer;">Attia's Tuto</h2>
? ? </div>
? ? <div class="nav_menu" id="nav_menu">
? ? ? <button class="close_btn" id="close_btn">
? ? ? ? <i class="ri-close-fill"></i>
? ? ? </button>
? ? ? <ul class="nav_menu_list">
? ? ? ? <li class="nav_menu_item" routerLinkActive="active" *ngIf="isLoggedIn">
? ? ? ? ? <a routerLink="/User" class="nav_menu_link">User</a>
? ? ? ? </li>
? ? ? ? <li class="nav_menu_item" routerLinkActive="active" *ngIf="isLoggedIn">
? ? ? ? ? <a routerLink="/Admin" class="nav_menu_link">Admin</a>
? ? ? ? </li>
? ? ? ? <li class="nav_menu_item" *ngIf="isLoggedIn" style="cursor: pointer;">
? ? ? ? ? <a (click)="signOut()" class="nav_menu_link">LogOut</a>
? ? ? ? </li>
? ? ? ? <li class="nav_menu_item" routerLinkActive="active" *ngIf="!isLoggedIn">
? ? ? ? ? <a routerLink="/SignIn" class="nav_menu_link">SignIn</a>
? ? ? ? </li>
? ? ? ? <li class="nav_menu_item" routerLinkActive="active" *ngIf="!isLoggedIn">
? ? ? ? ? <a routerLink="/SignUp" class="nav_menu_link">SignUp</a>
? ? ? ? </li>
? ? ? ? <li class="nav_menu_item" *ngIf="isLoggedIn">
? ? ? ? ? <p class="toggle_btn">
? ? ? ? ? ? {{getUserName()}}
? ? ? ? ? </p>
? ? ? ? </li>
? ? ? </ul>
? ? </div>
? </nav>
? <hr>
</header>
<router-outlet></router-outlet>
Once the navigation bar is set up, we can further style it using CSS to achieve the desired visual appearance. You can edit the separate CSS file, such as app.component.css, and add styles specific to the navigation bar.
/* ==== CSS VARIABLES ==== */
:root {
? --primary-color: #335eea;
? --link-color: #506690;
? --active-color: #d90c0c;
? --btn-hover-color: #2b50c7;
? --lg-heading: #161c2d;
? --text-content: #869ab8;
? --fixed-header-height: 4.5rem;
}
ul li {
? list-style-type: none;
}
a {
? text-decoration: none;
}
button {
? background-color: transparent;
? border: none;
? outline: none;
? cursor: pointer;
}
/* ==== CONTAINER ==== */
.container {
? width: 100%;
? margin-top: 20px;
}
@media screen and (min-width: 1040px) {
? .container {
? ? width: 1040px;
? ? margin-top: 20px;
? }
}
/* ==== HEADER ==== */
.header {
? height: var(--fixed-header-height);
? padding: 0 1.7rem;
}
/* ==== NAV ==== */
.nav {
? width: 100%;
? height: 100%;
? display: flex;
? align-items: center;
? justify-content: space-between;
}
/* ==== LOGO ==== */
.logo h2 {
? font-size: 28px;
? color: var(--primary-color);
}
/* ==== ?NAV-MENU ?==== */
.nav_menu_list {
? display: flex;
? align-items: center;
}
.nav_menu_list .nav_menu_item {
? margin: 0 2rem;
}
li.active{
? color: red;
}
.nav_menu_item .nav_menu_link {
? font-size: 16.5px;
? line-height: 27px;
? color: var(--link-color);
? text-transform: capitalize;
? letter-spacing: 0.5px;
}
.nav_menu_link:hover {
? color: var(--primary-color);
}
.toggle_btn {
? font-size: 20px;
? font-weight: 600;
? color: var(--lg-heading);
? z-index: 4;
}
.nav_menu,
.close_btn {
? display: none;
}
.show {
? right: 3% !important;
}
/* ==== MEDIA QURIES FOR RESPONSIVE DESIGN ==== */
@media screen and (min-width: 768px) {
? .nav_menu {
? ? display: block;
? }
}
@media screen and (max-width: 768px) {
? .logo h2 {
? ? font-size: 23px;
? }
? .nav_menu {
? ? position: fixed;
? ? width: 93%;
? ? height: 100%;
? ? display: block;
? ? top: 2.5%;
? ? right: -100%;
? ? background-color: #fff;
? ? padding: 3rem;
? ? border-radius: 10px;
? ? box-shadow: 0 0.5rem 1.5rem rgba(22, 28, 45, 0.1);
? ? z-index: 50;
? ? transition: 0.4s;
? }
? .nav_menu_list {
? ? flex-direction: column;
? ? align-items: flex-start;
? ? margin-top: 4rem;
? }
? .nav_menu_list .nav_menu_item {
? ? margin: 1rem 0;
? }
? .nav_menu_item .nav_menu_link {
? ? font-size: 18px;
? }
}
Now that we have completed the HTML structure and applied the necessary CSS styles, let's shift our attention to the app.component.ts file. This TypeScript component is where we establish the crucial connection between our HTML template and the underlying logic. By leveraging Angular's powerful data binding capabilities, we can effortlessly synchronize and update the UI based on changes in our application's state. Let's explore how we accomplish this by diving into the intricacies of the app.component.ts code.
import { AuthService } from './Services/auth.service';
@Component({
? selector: 'app-root',
? templateUrl: './app.component.html',
? styleUrls: ['./app.component.css']
})
export class AppComponent {
? ? // User Status
? ? isLoggedIn!: boolean;
? ? constructor(private authService : AuthService){}
? ? ngOnInit(): void {
// check if the token exist in session storage
? ? ? this.isLoggedIn = !!this.authService.getToken();
? ? }
? //get user Email Method
? getUserName() {
? ? return this.authService.getUser();
? }
? //Method to logout
? signOut() {
? ? this.authService.signOut();
? }
}
Designing the Login Page
After setting up the navigation bar, let's move on to designing the login page. The login page is a crucial component of any authentication system, allowing users to securely access their accounts. We want to create a visually appealing and user-friendly login interface.
In the login component's template, we can add the necessary HTML elements for the login form. This typically includes input fields for the email, password, and a submit button.
navigate to src/app/Components/login.component.html and edit it like this
<section>
? <div class="container-fluid h-custom">
? ? <div class="row d-flex justify-content-center align-items-center h-100">
? ? ? <div class="col-md-9 col-lg-6 col-xl-5">
? ? ? ? <img src="https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-login-form/draw2.webp" class="img-fluid"
? ? ? ? ? alt="Login Image">
? ? ? </div>
? ? ? <div class="col-md-8 col-lg-6 col-xl-4 offset-xl-1">
? ? ? ? <form [formGroup]="form" (ngSubmit)="onSubmit()"><br>
? ? ? ? ? <div class="d-flex flex-row align-items-center justify-content-center justify-content-lg-start">
? ? ? ? ? ? <p class="lead fw-normal mb-2 me-3"><b>Sign In</b></p>
? ? ? ? ? </div>
? ? ? ? ? <!-- Email input -->
? ? ? ? ? <mat-form-field class="form-group mb-3" appearance="fill">
? ? ? ? ? ? <mat-label class="form-label" for="Email">
? ? ? ? ? ? ? <i class="me-3"> <mat-icon> email</mat-icon></i> Email address
? ? ? ? ? ? </mat-label>
? ? ? ? ? ? <input matInput type="email" id="email" placeholder="Enter a valid email address" formControlName="email"
? ? ? ? ? ? ? [errorStateMatcher]="matcher" name="email" required />
? ? ? ? ? ? <mat-error *ngIf="email?.hasError('email') && !email?.hasError('required')">
? ? ? ? ? ? ? Please enter a valid <strong>email</strong> address
? ? ? ? ? ? </mat-error>
? ? ? ? ? ? <mat-error *ngIf="email?.hasError('required')">
? ? ? ? ? ? ? Email is <strong>required</strong>
? ? ? ? ? ? </mat-error>
? ? ? ? ? </mat-form-field>
? ? ? ? ? <!-- Password input -->
? ? ? ? ? <mat-form-field class="form-group mb-3" appearance="fill">
? ? ? ? ? ? <mat-label class="form-label" for="password">
? ? ? ? ? ? ? <i class="me-3"><mat-icon>lock</mat-icon></i> Password
? ? ? ? ? ? </mat-label>
? ? ? ? ? ? <input matInput type="password" id="password" placeholder="Enter password" formControlName="password"
? ? ? ? ? ? ? [errorStateMatcher]="matcher" [type]="hide ? 'password' : 'text'" name="password" required />
? ? ? ? ? ? <button type="button" mat-icon-button matSuffix (click)="hide = !hide" [attr.aria-label]="'Hide password'"
? ? ? ? ? ? ? [attr.aria-pressed]="hide">
? ? ? ? ? ? ? <mat-icon>{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
? ? ? ? ? ? </button>
? ? ? ? ? ? <mat-error *ngIf="password?.hasError('minlength') && !password?.hasError('required')">
? ? ? ? ? ? ? Password must have <strong>{{password?.errors?.['minlength'].requiredLength}}</strong> Letters ,You have
? ? ? ? ? ? ? Submitted <strong>{{password?.errors?.['minlength'].actualLength}}</strong> Letters
? ? ? ? ? ? </mat-error>
? ? ? ? ? ? <mat-error *ngIf=" password?.hasError('required')">
? ? ? ? ? ? ? Password is <strong>required</strong>
? ? ? ? ? ? </mat-error>
? ? ? ? ? </mat-form-field>
? ? ? ? ? <!--Login Button -->
? ? ? ? ? <div class="text-center mt-4 pt-2">
? ? ? ? ? ? <button mat-raised-button type="submit" color="primary" class="btn btn-primary btn-lg btn-block">
? ? ? ? ? ? ? Login
? ? ? ? ? ? </button>
? ? ? ? ? </div>
? ? ? ? ? <!--Redirectio to register components -->
? ? ? ? ? <div class="text-center text-lg-start mt-4 pt-2">
? ? ? ? ? ? <p class="small mt-2 pt-1 mb-0">Don't have an account?
? ? ? ? ? ? ? <a routerLink="/register" class="link-danger">Register</a>
? ? ? ? ? ? </p>
? ? ? ? ? </div>
? ? ? ? </form>
? ? ? </div>
? ? </div>
? </div>
</section>
From the HTML structure of the login component, let's transition to the CSS file where we define the styling rules for the login form.
.divider:after
.divider:before {
content: "";
flex: 1;
height: 3px;
background: #eee;
}
.h-custom {
height: calc(100% - 73px);
}
@media (max-width: 450px) {
.h-custom {
height: 100%;
}
}
.form-group {
? min-width: 150px;
? max-width: 500px;
? width: 100%;
}
section {
? display: block;
? height:78%;
? padding: 10px;
? padding-left: 120px;
? box-sizing:border-box;
}
@media (max-width: 1025px) {
? section {
? ? display: block;
? ? height:100%;
? ? width:99%;
? ? padding: 10px;
? ? margin-bottom: 120px;
? ? box-sizing:border-box;
? }
?}
@media (max-width: 450px) {
? section {
? ? display: block;
? ? height:78%;
? ? width:99%;
? ? padding: 10px;
? ? margin-bottom: 120px;
? ? box-sizing:border-box;
? }
?}
@media (max-width: 440px) {
? section {
? ? display: block;
? ? height:100%;
? ? width:100%;
? ? margin-bottom: 70px;
? ? box-sizing:border-box;
? }
}
Now, let's navigate to the `login.component.ts` file to examine the TypeScript code responsible for the functionality of our login component. By exploring this file, we can gain insights into how the login functionality is implemented and how it interacts with the corresponding HTML template.
import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { UserService } from 'src/app/Services/user.service';
import { AuthService } from 'src/app/Services/auth.service';
import { ErrorsStateMatcher } from 'src/app/Error-state-matcher';
@Component({
? selector: 'app-login',
? templateUrl: './login.component.html',
? styleUrls: ['./login.component.css'],
})
export class LoginComponent {
? constructor(
? ? private userService: UserService,
? ? private authService: AuthService,
? ? private _snackBar: MatSnackBar
? ) {}
? //Declaration
? //Check the form is submitted or not yet
? isSubmited: boolean = false;
? //Hide attribute for the password input
? hide: boolean = true;
? //Login is failed case
? isLoginFailed = false;
? //To display Login Error in case of failure
? errorMessage = '';
? //form validators
? form: FormGroup = new FormGroup({
? ? email: new FormControl('', [Validators.required, Validators.email]),
? ? password: new FormControl('', [
? ? ? Validators.required,
? ? ? Validators.minLength(8),
? ? ]),
? });
? //get all Form Fields
? get email() {
? ? return this.form.get('email');
? }
? get password() {
? ? return this.form.get('password');
? }
? // match errors in the submition of form
? matcher = new ErrorsStateMatcher();
? // submit fntc
? onSubmit() {
? ? const LoginInfo = {
? ? ? email: this.email?.value,
? ? ? password: this.password?.value,
? ? };
? ? if (this.form.valid) {
? ? ? this.userService.signIn(LoginInfo).subscribe({
? ? ? ? next: (data: any) => {
? ? ? ? ? this.authService.saveToken(data.token);
? ? ? ? ? this.isLoginFailed = false;
? ? ? ? ? window.location.reload();
? ? ? ? },
? ? ? ? error: (err: Error) => {
? ? ? ? ? this.errorMessage = err.message;
? ? ? ? ? this.isLoginFailed = true;
? ? ? ? ? this._snackBar.open(this.errorMessage, '?');
? ? ? ? },
? ? ? });
? ? } else {
? ? ? this._snackBar.open('Enter a valid informations !!!', '?');
? ? }
? }
}
Upon closer inspection, you'll notice a newly imported class called `ErrorsStateMatcher`. To incorporate this class into our project, we need to create it within the `src/app` folder. Let's proceed with adding this class to enhance the error state matching functionality within our application.
import { FormControl, FormGroupDirective, NgForm } from "@angular/forms"
import { ErrorStateMatcher } from "@angular/material/core";
export class ErrorsStateMatcher implements ErrorStateMatcher {
? //match errors in the submition : we can add control.touched or control.dirty like : (isSubmitted || control.touched)
? isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
? ? const isSubmitted = form && form.submitted;
? ? return !!(control && control.invalid && isSubmitted);
? }
};
The ErrorsStateMatcher class in the provided code is an implementation of the ErrorStateMatcher interface from Angular Material. It is used to determine the error state of a form control based on specific conditions.
The ErrorStateMatcher interface provides a method called isErrorState, which takes a form control and a form object as parameters and returns a boolean value indicating whether the control is in an error state.
In the ErrorsStateMatcher class, the isErrorState method is overridden to define the conditions for determining the error state of a form control. Here's a breakdown of what the code does:
By using this ErrorsStateMatcher class in conjunction with Angular Material form controls, you can define custom conditions to determine the error state of form controls. This allows you to control how error messages or styles are displayed based on specific criteria, such as when a form is submitted or when a control is touched or dirty.
Make sure to import and use this ErrorsStateMatcher class in the appropriate component where you want to apply custom error matching logic for your form controls.
Before so you need to import it in app.module.ts like the code below :
import { ErrorStateMatcher, ShowOnDirtyErrorStateMatcher } from '@angular/material/core';
providers: [
// other imports
{provide: ErrorStateMatcher, useClass: ShowOnDirtyErrorStateMatcher}
],
Designing the Registration Component
After creating the login page, let's move on to designing the registration component. The registration page allows users to create new accounts and join our platform. We want to provide a seamless and intuitive registration process for our users.
In the registration component's template, we can add the necessary HTML elements for the registration form. This typically includes input fields for the firstname, lastname, email, password, confirm password and a submit button.
Follow this code below in register.component.html
<section>
? <div class="card-body">
? ? <div class="row justify-content-center">
? ? ? <div class="col-md-10 col-lg-6 col-xl-5 order-2 order-lg-1">
? ? ? ? <form [formGroup]="form" class="mx-1 mx-md-4" (ngSubmit)="onSubmit()">
? ? ? ? ?
<!-- FirstName input -->
? ? ? ? ? <div class="d-flex flex-row align-items-center mb-3">
? ? ? ? ? ? <i class="me-3">
? ? ? ? ? ? ? <mat-icon>
? ? ? ? ? ? ? ? account_circle
? ? ? ? ? ? ? </mat-icon>
? ? ? ? ? ? </i>
? ? ? ? ? ? <mat-form-field class="form-outline flex-fill mb-0" appearance="fill">
? ? ? ? ? ? ? <mat-label class="form-label" for="firstname">
? ? ? ? ? ? ? ? First Name
? ? ? ? ? ? ? </mat-label>
? ? ? ? ? ? ? <input matInput type="text" id="firstname" placeholder="Enter a valid username"
? ? ? ? ? ? ? ? formControlName="firstname" [errorStateMatcher]="matcher" name="firstname" required />
? ? ? ? ? ? ? <mat-error *ngIf="firstname?.hasError('minlength') && !firstname?.hasError('required')">
? ? ? ? ? ? ? ? Firstname must have <strong>{{firstname?.errors?.['minlength'].requiredLength}}</strong> Letters ,You
? ? ? ? ? ? ? ? have Submitted <strong>{{firstname?.errors?.['minlength'].actualLength}}</strong> Letters
? ? ? ? ? ? ? </mat-error>
? ? ? ? ? ? ? <mat-error *ngIf="firstname?.hasError('required')">
? ? ? ? ? ? ? ? Firstname is <strong>required</strong>
? ? ? ? ? ? ? </mat-error>
? ? ? ? ? ? </mat-form-field>
? ? ? ? ? </div>
? ? ? ? ? <!-- Lastname input -->
? ? ? ? ? <div class="d-flex flex-row align-items-center mb-3">
? ? ? ? ? ? <i class="me-3">
? ? ? ? ? ? ? <mat-icon>
? ? ? ? ? ? ? ? account_circle
? ? ? ? ? ? ? </mat-icon>
? ? ? ? ? ? </i>
? ? ? ? ? ? <mat-form-field class="form-outline flex-fill mb-0" appearance="fill">
? ? ? ? ? ? ? <mat-label class="form-label" for="lastname">
? ? ? ? ? ? ? ? Last Name
? ? ? ? ? ? ? </mat-label>
? ? ? ? ? ? ? <input matInput type="text" id="lastname" placeholder="Enter a valid lastname" formControlName="lastname"
? ? ? ? ? ? ? ? [errorStateMatcher]="matcher" name="lastname" required />
? ? ? ? ? ? ? <mat-error *ngIf="lastname?.hasError('minlength') && !lastname?.hasError('required')">
? ? ? ? ? ? ? ? Lastname must have <strong>{{lastname?.errors?.['minlength'].requiredLength}}</strong> Letters ,You have
? ? ? ? ? ? ? ? Submitted <strong>{{lastname?.errors?.['minlength'].actualLength}}</strong> Letters
? ? ? ? ? ? ? </mat-error>
? ? ? ? ? ? ? <mat-error *ngIf="lastname?.hasError('required')">
? ? ? ? ? ? ? ? Lastname is <strong>required</strong>
? ? ? ? ? ? ? </mat-error>
? ? ? ? ? ? </mat-form-field>
? ? ? ? ? </div>
? ? ? ? ? <!-- Email input -->
? ? ? ? ? <div class="d-flex flex-row align-items-center mb-3">
? ? ? ? ? ? <i class="me-3">
? ? ? ? ? ? ? <mat-icon>
? ? ? ? ? ? ? ? email
? ? ? ? ? ? ? </mat-icon>
? ? ? ? ? ? </i>
? ? ? ? ? ? <mat-form-field class="form-outline flex-fill mb-0" appearance="fill">
? ? ? ? ? ? ? <mat-label class="form-label" for="Email">
? ? ? ? ? ? ? ? Email address
? ? ? ? ? ? ? </mat-label>
? ? ? ? ? ? ? <input matInput type="email" id="email" placeholder="Enter a valid email address" formControlName="email"
? ? ? ? ? ? ? ? [errorStateMatcher]="matcher" name="email" required />
? ? ? ? ? ? ? <mat-error *ngIf="email?.hasError('email') && !email?.hasError('required')">
? ? ? ? ? ? ? ? Please enter a valid <strong>email</strong> address
? ? ? ? ? ? ? </mat-error>
? ? ? ? ? ? ? <mat-error *ngIf="email?.hasError('required')">
? ? ? ? ? ? ? ? Email is <strong>required</strong>
? ? ? ? ? ? ? </mat-error>
? ? ? ? ? ? </mat-form-field>
? ? ? ? ? </div>
? ? ? ? ? <!-- Password input -->
? ? ? ? ? <div class="d-flex flex-row align-items-center mb-3">
? ? ? ? ? ? <i class="me-3">
? ? ? ? ? ? ? <mat-icon>
? ? ? ? ? ? ? ? lock
? ? ? ? ? ? ? </mat-icon>
? ? ? ? ? ? </i>
? ? ? ? ? ? <mat-form-field class="form-outline flex-fill mb-0" appearance="fill">
? ? ? ? ? ? ? <mat-label class="form-label" for="password">
? ? ? ? ? ? ? ? Password
? ? ? ? ? ? ? </mat-label>
? ? ? ? ? ? ? <input matInput type="password" id="password" placeholder="Enter password" formControlName="password"
? ? ? ? ? ? ? ? [errorStateMatcher]="matcher" [type]="hide ? 'password' : 'text'" name="password" required />
? ? ? ? ? ? ? <button type="button" mat-icon-button matSuffix (click)="hide = !hide" [attr.aria-label]="'Hide password'"
? ? ? ? ? ? ? ? [attr.aria-pressed]="hide">
? ? ? ? ? ? ? ? <mat-icon>{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
? ? ? ? ? ? ? </button>
? ? ? ? ? ? ? <mat-error *ngIf=" password?.hasError('pattern') && !password?.hasError('required')">
? ? ? ? ? ? ? ? Must contain at least <strong>one number</strong> and <strong>one uppercase</strong> and
? ? ? ? ? ? ? ? <strong>lowercase letters</strong> ,
? ? ? ? ? ? ? ? and at <strong>least 8</strong> or more characters.
? ? ? ? ? ? ? </mat-error>
? ? ? ? ? ? ? <mat-error *ngIf="password?.hasError('minlength') && !password?.hasError('required')">
? ? ? ? ? ? ? ? Password must have <strong>{{password?.errors?.['minlength'].requiredLength}}</strong> Letters ,You have
? ? ? ? ? ? ? ? Submitted <strong>{{password?.errors?.['minlength'].actualLength}}</strong> Letters
? ? ? ? ? ? ? </mat-error>
? ? ? ? ? ? ? <mat-error *ngIf=" password?.hasError('required')">
? ? ? ? ? ? ? ? Password is <strong>required</strong>
? ? ? ? ? ? ? </mat-error>
? ? ? ? ? ? </mat-form-field>
? ? ? ? ? </div>
? ? ? ? ? <!-- Confirmation Password input -->
? ? ? ? ? <div class="d-flex flex-row align-items-center mb-3">
? ? ? ? ? ? <i class="me-3">
? ? ? ? ? ? ? <mat-icon>
? ? ? ? ? ? ? ? vpn_key
? ? ? ? ? ? ? </mat-icon>
? ? ? ? ? ? </i>
? ? ? ? ? ? <mat-form-field class="form-outline flex-fill mb-0" appearance="fill">
? ? ? ? ? ? ? <mat-label class="form-label" for="cPassword">
? ? ? ? ? ? ? ? Confirm Password
? ? ? ? ? ? ? </mat-label>
? ? ? ? ? ? ? <input matInput type="password" id="cPassword" placeholder="Enter password" formControlName="cPassword"
? ? ? ? ? ? ? ? [errorStateMatcher]="matcher" [type]="hide ? 'password' : 'text'" name="cPassword" required />
? ? ? ? ? ? ? <button type="button" mat-icon-button matSuffix (click)="hide = !hide" [attr.aria-label]="'Hide password'"
? ? ? ? ? ? ? ? [attr.aria-pressed]="hide">
? ? ? ? ? ? ? ? <mat-icon>{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
? ? ? ? ? ? ? </button>
? ? ? ? ? ? ? <mat-error *ngIf="cPassword?.hasError('passwordMismatch')">
? ? ? ? ? ? ? ? Password does not match
? ? ? ? ? ? ? </mat-error>
? ? ? ? ? ? ? <mat-error *ngIf=" cPassword?.hasError('required')">
? ? ? ? ? ? ? ? Password confirmation is <strong>required</strong>
? ? ? ? ? ? ? </mat-error>
? ? ? ? ? ? </mat-form-field>
? ? ? ? ? </div>
? ? ? ? ? <!--Register Button-->
? ? ? ? ? <div class="d-flex justify-content-center mx-4 mb-3 mb-lg-4">
? ? ? ? ? ? <button mat-raised-button type="submit" color="primary" class="btn btn-primary btn-lg btn-block">
? ? ? ? ? ? ? Register
? ? ? ? ? ? </button>
? ? ? ? ? </div>
? ? ? ? </form>
? ? ? </div>
? ? ? <!--Image-->
? ? ? <div class="col-md-9 col-lg-6 col-xl-7 d-flex align-items-center order-1 order-lg-2">
? ? ? ? <img src="https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-registration/draw1.webp" class="img-fluid"
? ? ? ? ? alt="Register Image">
? ? ? </div>
? ? </div>
? </div>
</section>
Next let's edit the register.component.ts
import { Component } from '@angular/core';
import {
? AbstractControl,
? FormControl,
? FormGroup,
? ValidationErrors,
? Validators,
} from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ErrorsStateMatcher } from 'src/app/Error-state-matcher';
import { UserService } from 'src/app/Services/user.service';
@Component({
? selector: 'app-register',
? templateUrl: './register.component.html',
? styleUrls: ['./register.component.css'],
})
export class RegisterComponent {
? constructor(
? ? private userService: UserService,
? ? private _snackBar: MatSnackBar
? ) {}
? //Declaration
? // check the form is submitted or not yet
? isSubmited: boolean = false;
? // hide attribute for the password input
? hide: boolean = true;
? //form group
? form: FormGroup = new FormGroup(
? ? {
? ? ? firstname: new FormControl('', [
? ? ? ? Validators.required,
? ? ? ? Validators.minLength(4),
? ? ? ]),
? ? ? lastname: new FormControl('', [
? ? ? ? Validators.required,
? ? ? ? Validators.minLength(4),
? ? ? ]),
? ? ? email: new FormControl('', [Validators.required, Validators.email]),
? ? ? password: new FormControl('', [
? ? ? ? Validators.required,
? ? ? ? Validators.minLength(8),
? ? ? ? Validators.pattern('(?=.*d)(?=.*[a-z])(?=.*[A-Z]).{8,}'),
? ? ? ]),
? ? ? cPassword: new FormControl('', [Validators.required]),
? ? },
? ? {
? ? ? validators: this.passwordMatch('password', 'cPassword'),
? ? }
? );
? //get all Form Fields
? get firstname() {
? ? return this.form.get('firstname');
? }
? get lastname() {
? ? return this.form.get('lastname');
? }
? get email() {
? ? return this.form.get('email');
? }
? get password() {
? ? return this.form.get('password');
? }
? get cPassword() {
? ? return this.form.get('cPassword');
? }
? // match errors in the submition of form
? matcher = new ErrorsStateMatcher();
? // submit fntc
? onSubmit() {
? ? // TODO: Use EventEmitter with form value
? ? this.isSubmited = true;
? ? if (!this.form.invalid) {
? ? ? const user = {
? ? ? ? firstname: this.firstname?.value,
? ? ? ? lastname: this.lastname?.value,
? ? ? ? email: this.email?.value,
? ? ? ? password: this.password?.value,
? ? ? ? admin: false,
? ? ? };
? ? ? console.log(user);
? ? ? this.userService.Create(user).subscribe(() => {
? ? ? ? this._snackBar.open('Your account has been created successfully', '??');
? ? ? ? setTimeout(() => (window.location.href = '/SignIn'), 2000);
? ? ? });
? ? } else {
? ? ? console.log(this.form);
? ? ? this._snackBar.open('Enter a valid informations !!!', '?');
? ? }
? }
? // check the password and confirm password are matched or not
? passwordMatch(password: string, confirmPassword: string) {
? ? return (formGroup: AbstractControl): ValidationErrors | null => {
? ? ? const passwordControl = formGroup.get(password);
? ? ? const confirmPasswordControl = formGroup.get(confirmPassword);
? ? ? if (!passwordControl || !confirmPasswordControl) {
? ? ? ? return null;
? ? ? }
? ? ? if (
? ? ? ? confirmPasswordControl.errors &&
? ? ? ? !confirmPasswordControl.errors['passwordMismatch']
? ? ? ) {
? ? ? ? return null;
? ? ? }
? ? ? if (passwordControl.value !== confirmPasswordControl.value) {
? ? ? ? confirmPasswordControl.setErrors({ passwordMismatch: true });
? ? ? ? return { passwordMismatch: true };
? ? ? } else {
? ? ? ? confirmPasswordControl.setErrors(null);
? ? ? ? return null;
? ? ? }
? ? };
? }
}
Let's focus on the home.component.html file, which serves as the face of our application. In this static component, we define the structure and content that will be displayed to the users when they land on the home page. It acts as the introductory interface, providing a visually appealing and informative experience to engage our users right from the start.
<section class="wrapper">
? <div class="container">
? ? <div class="grid-cols-2">
? ? ? <div class="grid-item-1">
? ? ? ? <h1 class="main-heading">
? ? ? ? ? Welcome to <span>Attia Imed</span>
? ? ? ? ? <br />
? ? ? ? ? Linkedin tutorial.
? ? ? ? </h1>
? ? ? ? <p class="info-text">
? ? ? ? ? Build a beautiful, modern website with flexible components built
? ? ? ? ? from scratch.
? ? ? ? </p>
? ? ? ? <div class="btn_wrapper">
? ? ? ? ? <a href="https://www.dhirubhai.net/in/attia-imeed/recent-activity/all/" class="btn view_more_btn">
? ? ? ? ? ? view all Posts
? ? ? ? ? </a>
? ? ? ? ? <button class="btn documentation_btn"><a href="mailto:[email protected]">Contact Me</a></button>
? ? ? ? </div>
? ? ? </div>
? ? ? <div class="grid-item-2">
? ? ? ? <div class="team_img_wrapper">
? ? ? ? ? <img src="./../assets/team.svg" alt="team-img" />
? ? ? ? </div>
? ? ? </div>
? ? </div>
? </div>
</section>
<section class="wrapper">
? <div class="container" data-aos="fade-up" data-aos-duration="1000">
? ? <div class="grid-cols-3">
? ? ? <div class="grid-col-item">
? ? ? ? <div class="icon">
? ? ? ? ? <svg xmlns="https://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
? ? ? ? ? ? <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
? ? ? ? ? ? ? d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
? ? ? ? ? </svg>
? ? ? ? </div>
? ? ? ? <div class="featured_info">
? ? ? ? ? <span><a
? ? ? ? ? ? ? href="https://www.dhirubhai.net/pulse/building-full-stack-web-application-angular-15-spring-attia-imed/">Building
? ? ? ? ? ? ? a Full-Stack Web Application with Angular 15 and Spring Boot 3 from SCRATCH (2023)</a></span>
? ? ? ? ? <p>
? ? ? ? ? ? Learn to build a full-stack web app with Angular 15, Spring Boot 3, and MongoDB. Develop seamless user
? ? ? ? ? ? experiences, implement RESTful APIs, and leverage powerful frontend and backend technologies. Level up your
? ? ? ? ? ? skills in routing, HTTP requests, JSON data, and more. Join us on this hands-on tutorial and unlock the
? ? ? ? ? ? potential of full-stack development.
? ? ? ? ? </p>
? ? ? ? </div>
? ? ? </div>
? ? ? <div class="grid-col-item">
? ? ? ? <div class="icon">
? ? ? ? ? <svg xmlns="https://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
? ? ? ? ? ? <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
? ? ? ? ? ? ? d="M17 14v6m-3-3h6M6 10h2a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2zm10 0h2a2 2 0 002-2V6a2 2 0 00-2-2h-2a2 2 0 00-2 2v2a2 2 0 002 2zM6 20h2a2 2 0 002-2v-2a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2z" />
? ? ? ? ? </svg>
? ? ? ? </div>
? ? ? ? <div class="featured_info">
? ? ? ? ? <span><a href="https://www.dhirubhai.net/pulse/deploy-angular-14-application-firebase-attia-imed/">Deploy
? ? ? ? ? ? ? Angular 14 application on Firebase</a></span>
? ? ? ? ? <p>
? ? ? ? ? ? Deploy Angular 14 application on Firebase
? ? ? ? ? ? This tutorial Will cover all necessary steps To deploy your Angular application on Firebase Hosting from
? ? ? ? ? ? sctarch as well as it contains all documentation and explanation.
? ? ? ? ? </p>
? ? ? ? </div>
? ? ? </div>
? ? ? <div class="grid-col-item">
? ? ? ? <div class="icon">
? ? ? ? ? <svg xmlns="https://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
? ? ? ? ? ? <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
? ? ? ? ? ? ? d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
? ? ? ? ? </svg>
? ? ? ? </div>
? ? ? ? <div class="featured_info">
? ? ? ? ? <span><a href="https://www.dhirubhai.net/pulse/angular-14-firebase-authentication-tutorial-attia-imed/">Angular
? ? ? ? ? ? ? 14 Firebase Authentication Tutorial
? ? ? ? ? ? </a></span>
? ? ? ? ? <p>
? ? ? ? ? ? Firebase Authentication System on Angular 14 application.
? ? ? ? ? ? This tutorial Will cover all necessary steps To make an Auth System to your Angular application with
? ? ? ? ? ? Firebase Authentication from sctarch as well as it contains all documentation and explanation.
? ? ? ? ? </p>
? ? ? ? </div>
? ? ? </div>
? ? </div>
? </div>
</section>
In addition to the HTML file, we can also explore the accompanying CSS file to enhance the visual presentation of the home component. By applying styles and layouts, we can create a captivating and consistent visual identity that aligns with the overall design of our application.
/* ==== "Inter" FONT-FAMILY FROM FONTS.GOOGLE.COM ?==== */
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap");
/* ==== ROOT RESET ==== */
* {
? margin: 0;
? padding: 0;
? box-sizing: border-box;
? font-family: "Inter", sans-serif;
}
*,
*::before,
*::after {
? box-sizing: border-box;
}
/* ==== CSS VARIABLES ==== */
:root {
? --primary-color: #335eea;
? --link-color: #506690;
? --active-color: #d90c0c;
? --btn-hover-color: #2b50c7;
? --lg-heading: #161c2d;
? --text-content: #869ab8;
? --fixed-header-height: 4.5rem;
}
/* ==== RESET HTML ==== */
body {
? width: 100%;
? height: 100vh;
? overflow-x: hidden;
? background-color: #fafbfb;
}
ul li {
? list-style-type: none;
}
a {
? text-decoration: none;
}
button {
? background-color: transparent;
? border: none;
? outline: none;
? cursor: pointer;
}
/* ==== CONTAINER ==== */
.container {
? width: 100%;
? margin: 0 auto;
}
@media screen and (min-width: 1040px) {
? .container {
? ? width: 1040px;
? ? margin: 0 auto;
? }
}
/* ==== HEADER ==== */
.header {
? height: var(--fixed-header-height);
? padding: 0 1.7rem;
}
/* ==== LOGO ==== */
.logo h2 {
? font-size: 28px;
? color: var(--primary-color);
}
/* ==== ?NAV-MENU ?==== */
.nav_menu_list {
? display: flex;
? align-items: center;
}
.nav_menu_list .nav_menu_item {
? margin: 0 2rem;
}
li.active{
? color: green;
}
.nav_menu_item .nav_menu_link {
? font-size: 16.5px;
? line-height: 27px;
? color: var(--link-color);
? text-transform: capitalize;
? letter-spacing: 0.5px;
}
.nav_menu_link:hover {
? color: var(--primary-color);
}
.toggle_btn {
? font-size: 20px;
? font-weight: 600;
? color: var(--lg-heading);
? z-index: 4;
}
.nav_menu,
.close_btn {
? display: none;
}
.show {
? right: 3% !important;
}
/* ==== ?WRAPPER ==== */
.wrapper {
? width: 100%;
? padding-left: 1.7rem;
? padding-right: 1.7rem;
? margin-bottom: 5rem;
}
.grid-cols-2 {
? width: 100%;
? height: 100%;
? display: grid;
? grid-template-columns: repeat(2, 1fr);
? gap: 4rem;
}
.grid-item-1 {
? padding-top: 5rem;
? padding-left: 1.5rem;
}
.main-heading {
? font-weight: 300;
? font-size: 40px;
? line-height: 55px;
}
.main-heading span {
? color: var(--primary-color);
}
.info-text {
? margin-top: 1.5rem;
? font-size: 19px;
? line-height: 28px;
? color: #334157;
}
.btn_wrapper {
? margin-top: 3.5rem;
? display: flex;
? width: 100%;
}
.btn {
? width: 110px;
? height: 50px;
? background-color: var(--primary-color);
? display: block;
? font-size: 16px;
? color: #fff;
? text-transform: capitalize;
? border-radius: 7px;
? letter-spacing: 1px;
? transition: 0.4s;
}
.btn:hover {
? transform: translateY(-3px);
? background-color: var(--btn-hover-color);
}
.view_more_btn {
? width: 180px;
? height: 55px;
? display: flex;
? align-items: center;
? justify-content: center;
? font-size: 16px;
? letter-spacing: 0;
? color: #3d186d;
? font-weight: 500;
? margin-right: 10px;
? box-shadow: 0 0.5rem 1.5rem rgba(22, 28, 45, 0.1);
}
.view_more_btn i {
? margin-left: 0.7rem;
}
.view_more_btn:hover {
? transition: box-shadow 0.25s ease, transform 0.25s ease;
}
.documentation_btn {
? width: 150px;
? height: 55px;
? font-size: 16px;
? font-weight: 500;
? color: #fff;
? letter-spacing: 0;
? background-color: #e1e7fc;
? color: #0e2a86;
? box-shadow: 0 0.5rem 1.5rem rgba(22, 28, 45, 0.1);
}
.documentation_btn:hover {
? background-color: #d7ddf1;
? transition: box-shadow 0.25s ease, transform 0.25s ease;
}
.grid-item-2 {
? width: 100%;
? height: 100%;
}
.team_img_wrapper {
? width: 500px;
? max-width: 100%;
? height: 440px;
}
.team_img_wrapper img {
? width: 100%;
? height: 100%;
? object-fit: contain;
}
.grid-cols-3 {
? width: 100%;
? height: 100%;
? display: grid;
? grid-template-columns: repeat(3, 1fr);
? column-gap: 3rem;
? row-gap: 2rem;
? padding: 1rem;
}
.grid-col-item {
? height: 100%;
}
.icon {
? width: 100%;
? line-height: 40px;
}
.icon svg {
? width: 30px;
? height: 30px;
? color: #6b85d8;
}
.featured_info {
? width: 100%;
}
.featured_info span {
? width: 100%;
? display: block;
? font-size: 21px;
? line-height: 33px;
? color: var(--lg-heading);
}
.featured_info p {
? display: block;
? width: 100%;
? margin-top: 7px;
? font-weight: 400;
? color: #334157;
? line-height: 25px;
? font-size: 15.5px;
}
footer {
? width: 100%;
? background-color: var(--primary-color);
? height: 12px;
? margin-top: 8rem;
}
/* ==== MEDIA QURIES FOR RESPONSIVE DESIGN ==== */
@media screen and (min-width: 768px) {
? .toggle_btn {
? ? display: none;
? }
? .nav_menu {
? ? display: block;
? }
}
@media screen and (max-width: 768px) {
? .logo h2 {
? ? font-size: 23px;
? }
? .nav_menu {
? ? position: fixed;
? ? width: 93%;
? ? height: 100%;
? ? display: block;
? ? top: 2.5%;
? ? right: -100%;
? ? background-color: #fff;
? ? padding: 3rem;
? ? border-radius: 10px;
? ? box-shadow: 0 0.5rem 1.5rem rgba(22, 28, 45, 0.1);
? ? z-index: 50;
? ? transition: 0.4s;
? }
? .nav_menu_list {
? ? flex-direction: column;
? ? align-items: flex-start;
? ? margin-top: 4rem;
? }
? .nav_menu_list .nav_menu_item {
? ? margin: 1rem 0;
? }
? .nav_menu_item .nav_menu_link {
? ? font-size: 18px;
? }
? .close_btn {
? ? display: block;
? ? position: absolute;
? ? right: 10%;
? ? font-size: 25px;
? ? color: #50689e;
? }
? .close_btn:hover {
? ? color: #000;
? }
? .wrapper {
? ? padding: 1px;
? }
? .grid-item-1 {
? ? padding-left: 0rem;
? }
? .main-heading {
? ? font-size: 35px;
? }
? .view_more_btn {
? ? width: 140px;
? ? height: 55px;
? ? font-size: 13.5px;
? ? margin-right: 1rem;
? }
? .grid-cols-3 {
? ? grid-template-columns: repeat(auto-fit, minmax(100%, 1fr));
? }
? .featured_info p {
? ? line-height: 23px;
? ? font-size: 14px;
? }
}
@media screen and (max-width: 991px) {
? .wrapper {
? ? padding-top: 3rem;
? }
? .grid-cols-2 {
? ? grid-template-columns: repeat(auto-fit, minmax(100%, 1fr));
? }
? .grid-item-1 {
? ? order: 2;
? ? display: flex;
? ? flex-direction: column;
? ? align-items: center;
? ? justify-content: center;
? ? padding-top: 0;
? }
? .main-heading {
? ? font-size: 32px;
? ? text-align: center;
? ? line-height: 40px;
? }
? .info-text {
? ? font-size: 16px;
? ? text-align: center;
? ? padding: 0.7rem;
? }
? .btn_wrapper {
? ? width: 100%;
? ? display: flex;
? ? align-items: center;
? ? justify-content: center;
? }
? .grid-item-2 {
? ? order: 1;
? ? display: flex;
? ? flex-direction: column;
? ? align-items: center;
? ? justify-content: center;
? }
? .team_img_wrapper {
? ? width: 350px;
? ? height: 350px;
? }
? .featured_info span {
? ? font-size: 19px;
? }
}
Now, let's take a look at the user.component.html file, where we can find the code responsible for defining the user component's structure and content
<section class="vh-100" style="background-color: #eee;">
? <div class="container py-5 h-100">
? ? <div class="row d-flex justify-content-center align-items-center h-100">
? ? ? <div class="col-md-12 col-xl-4">
? ? ? ? <div class="card" style="border-radius: 15px;">
? ? ? ? ? <div class="card-body text-center">
? ? ? ? ? ? <div class="mt-3 mb-4">
? ? ? ? ? ? ? <img
? ? ? ? ? ? ? ? src="https://scontent.ftun15-1.fna.fbcdn.net/v/t39.30808-6/269839779_969055454004946_8214199365716942693_n.jpg?_nc_cat=104&cb=99be929b-3346023f&ccb=1-7&_nc_sid=730e14&_nc_ohc=lVdbpLDBQFUAX9fUsIu&_nc_ht=scontent.ftun15-1.fna&oh=00_AfBmk178R28QKC7TNmIT2zYOPzZ8O4KJgn0RdL90YJSSxQ&oe=64AADFC6"
? ? ? ? ? ? ? ? class="rounded-circle img-fluid" style="width: 100px;" />
? ? ? ? ? ? </div>
? ? ? ? ? ? <h4 class="mb-2">{{userInfo.firstname +" "+userInfo.lastname}}</h4>
? ? ? ? ? ? <p class="text-muted mb-4">@Programmer : {{userInfo.admin ? "Admin" : "User" }} <span class="mx-2">|</span>
? ? ? ? ? ? ? <a [href]="'mailto:'+[userInfo.email]">{{userInfo.email}}</a></p>
? ? ? ? ? </div>
? ? ? ? </div>
? ? ? </div>
? ? </div>
? </div>
</section>
Let's now shift our attention to the user.component.ts file
import { Component } from '@angular/core';
import { AuthService } from 'src/app/Services/auth.service';
import { UserService } from 'src/app/Services/user.service';
import { User } from 'src/app/models/user';
@Component({
? selector: 'app-user',
? templateUrl: './user.component.html',
? styleUrls: ['./user.component.css'],
})
export class UserComponent {
? //save userId in a varibale
? userId: string = '';
? userInfo: User = new User();
? constructor(
? ? private userService: UserService,
? ? private authService: AuthService
? ) {
? ? this.userId = this.authService.getUserId() as string;
? ? this.refreshProfile();
? }
? //get User Info
? refreshProfile() {
? ? this.userService.get(this.userId).subscribe((response: User) => {
? ? ? this.userInfo = response;
? ? });
? }
}
Let's focus on the admin.component.html file, which represents the layout and structure of the admin page. This page is exclusively accessible to users with admin roles, as indicated by the token header. It serves as a privileged area for performing administrative tasks or accessing sensitive information.
<table class="table" style="margin-top: 10px;">
? <thead>
? ? <tr>
? ? ? <th scope="col">#</th>
? ? ? <th scope="col">First</th>
? ? ? <th scope="col">Last</th>
? ? ? <th scope="col">Email</th>
? ? ? <th scope="col">Country</th>
? ? ? <th scope="col">City</th>
? ? </tr>
? </thead>
? <tbody>
? ? <tr *ngFor="let user of listUSers">
? ? ? <th scope="row">{{user.userId}}</th>
? ? ? <td>{{user.firstname}}</td>
? ? ? <td>{{user.lastname}}</td>
? ? ? <td>{{user.email}}</td>
? ? ? <td>{{user.addresses[0].country}}</td>
? ? ? <td>{{user.addresses[0].city}}</td>
? ? </tr>
? </tbody>
</table>
In the context of the admin.component.ts file, its role is to handle the functionality and rendering of the admin page once the guard has confirmed that the user has the necessary admin privileges. The guard, on the other hand, is responsible for evaluating the user's role and determining whether they are authorized to access the admin page. If the user does not possess the required admin role, the guard will prevent them from accessing this page altogether.
Therefore, in the admin.component.ts file, we can focus on implementing the specific functionality and features related to the admin page, knowing that the guard has already ensured that only authenticated users with admin privileges can reach this component.
import { Component } from '@angular/core';
import { UserService } from 'src/app/Services/user.service';
import { User } from 'src/app/models/user';
@Component({
? selector: 'app-admin',
? templateUrl: './admin.component.html',
? styleUrls: ['./admin.component.css']
})
export class AdminComponent {
? listUSers : User[] = [];
? constructor(private userService : UserService){
? ? this.getAllUSers();
? }
? getAllUSers(){
? ? this.userService.getAll().subscribe((response : User[])=>{
? ? ? this.listUSers = response;
? ? })
? }
Step 5: Testing the Authentication System
Now that we have implemented the authentication system in our Angular application, it's time to put it to the test. Follow these steps to ensure that everything is functioning correctly:
1. Start the development server by running the command
ng serve -o
in the command prompt or terminal. This will compile your Angular application and serve it locally on `https://localhost:4200`.
2. Open your web browser and visit `https://localhost:4200` to view your application.
3. As a user, navigate to the login page and verify that the login form is displayed correctly. Enter valid credentials and click the login button to authenticate.
4. If the login is successful, you should be redirected to the home page.
Ensure that the navigation bar is visible and that you can navigate between different components using the provided links.
5. Test the role-based access control functionality by attempting to access restricted pages. For example, if an authenticated user without admin privileges tries to access the admin page, they should be redirected to a different page and shown an error message indicating insufficient privileges.
6. Pay attention to any error messages or feedback displayed by the application. Verify that error handling is implemented correctly and that users receive appropriate messages when they encounter issues.
Note: Make sure you have the necessary backend services running to handle API requests and provide the expected data to the Angular frontend.
Keep the command prompt or terminal running to keep the development server active. You can make changes to your code and the server will automatically recompile and reflect the updates in the browser.
Now, let's take a closer look at our application in action. Below are some screenshots showcasing the different components, navigation, and data display. Please note that these screenshots provide a glimpse into the user interface and functionality of our application.
Lastly you can download the complete code of this tutorial from?Repository Link.
Backend used in this tutorial : Repository Link.
Conclusion
Congratulations! You have successfully implemented and tested the authentication system in your Angular application. By following the steps outlined in this tutorial, you have created a secure and role-based access control system that protects certain pages and features based on user roles. Feel free to further enhance and customize the authentication system according to your application's specific requirements.
Remember, in a production environment, it's essential to conduct thorough testing and consider additional security measures to ensure the integrity and reliability of your authentication system.
Thank you for joining us on this journey, and we wish you all the best in your future endeavors as a full-stack developer. Happy coding!
Développeur Angular
8 个月I have a small incomprehension about the comment, the name of your method and what your method returns. CHECK THE ASSOCIATED PICTURE.
Développeur Angular
8 个月Thank you for this article, The explanation is clear. But I don't think SecureInnerPagesGuard is necessary. AuthGuard alone is sufficient in the sense that if the user is logged in, he is redirected directly to the home page, otherwise he is redirected to the login page.
Associate Software Engineer @ Brain Station 23
1 年Great article, but I think there is a correction to be made. You've written that "....token-based authentication utilizes tokens that are generated, stored, and verified on the client side" but JWTs are not generated on the client side(as shown in your article).