top of page
  • Writer's pictureSolve It

#3 StoreStreamUi17: Create authentication logic and extend project architecture



Today's article contains code snippets and instructions for creating authentication-related components, routes, models, services, and utilities in an Angular application. It provides step-by-step instructions for setting up environment variables, creating folders and components, defining routes, creating classes and interfaces, and implementing services for authentication functionality. The code snippets are organized into sections and include explanations and comments to guide the developer through the process of implementing authentication features in the application.



Create environment variables

  ng g environments

Update environment variables

export const environment = {
  production: false,
  apiUrl: "http://localhost:3000/api",
};

Create auth folder

Create a new folder auth in the src/app folder

Create auth components

  cd src/app
  ng generate component auth/login
  ng generate component auth/register
  ng generate component auth/forgot-password
  ng generate component auth/reset-password

Create routes for auth module

Create a new file auth.routes.ts in the src/app/auth folder

import { Route } from "@angular/router";
import { ForgotPasswordComponent } from "./forgot-password/forgot-password.component";
import { LoginComponent } from "./login/login.component";
import { RegisterComponent } from "./register/register.component";
import { ResetPasswordComponent } from "./reset-password/reset-password.component";

export default [
  { path: "login", component: LoginComponent },
  { path: "register", component: RegisterComponent },
  { path: "forgot-password", component: ForgotPasswordComponent },
  { path: "reset-password", component: ResetPasswordComponent },
] satisfies Route[];

Update app.routes.ts file

import { Routes } from "@angular/router";

export const routes: Routes = [{ path: "auth", loadChildren: () => import("./auth/routes") }];

Create User class

Create a new folder classes in the src/app/shared folder

  cd src/app
  ng generate class shared/classes/user

user.ts

export class User {
  constructor(public _id: string, public email: string, public firstName: string, public lastName: string) {}
}

Create Auth models

Create a new folder interfaces in the src/app/shared folder

  cd src/app
  ng generate interface shared/interfaces/login
  ng generate interface shared/interfaces/register
  ng generate interface shared/interfaces/forgot-password
  ng generate interface shared/interfaces/reset-password
  ng generate interface shared/interfaces/auth
  ng generate interface shared/interfaces/token

login.ts

export interface Login {
  email: string;
  password: string;
}

register.ts

export interface Register {
  email: string;
  password: string;
  confirmPassword: string;
  firstName: string;
  lastName: string;
}

forgot-password.ts

export interface ForgotPassword {
  email: string;
}

reset-password.ts

export interface ResetPassword {
  password: string;
  token: string;
}

auth.ts

import { User } from "../classes/user";
import { Token } from "./token";

export interface Auth {
  user: User;
  tokens: {
    access: Token;
    refresh: Token;
  };
}

token.ts

export interface Token {
  token: string;
  expires: Date;
}

Create utils

Here we will create utility classes that will help us in the future. Create a new folder utils in the src/app/shared folder

  cd src/app
  ng generate class shared/utils/localStorage
  ng generate class shared/utils/jsonWebToken

localStorage.ts

export class LocalStorage {
  private static local = window.localStorage;

  /**
   * get item from local storage
   * @param key
   */
  public static getItem(key: string): string | null {
    return LocalStorage.local.getItem(key);
  }

  /**
   * set item to localstorage
   * @param key
   * @param value
   */
  public static setItem(key: string, value: string) {
    return LocalStorage.local.setItem(key, value);
  }

  /**
   * remove item
   * @param key
   */
  public static removeItem(key: string) {
    LocalStorage.local.removeItem(key);
  }

  /**
   * clear local storage
   */
  public static clear() {
    LocalStorage.local.clear();
  }
}

jsonWebToken.ts

import { Token } from "../interfaces/token";
import { LocalStorage } from "./local-storage";

export class JsonWebToken {
  /**
   * get token
   */
  static getToken() {
    return LocalStorage.getItem("token");
  }

  /**
   * get token expires
   */
  static getTokenExipres() {
    return LocalStorage.getItem("token-expires");
  }

  /**
   * get refresh token
   */
  static getRefreshToken() {
    return LocalStorage.getItem("refresh-token");
  }

  /**
   * set token
   * @param token
   */
  static setToken(token: Token) {
    LocalStorage.setItem("token", token.token);
    LocalStorage.setItem("token-expires", token.expires.toString());
  }

  /**
   * set refresh token
   * @param refreshToken
   */
  static setRefreshToken(refreshToken: Token) {
    LocalStorage.setItem("refresh-token", refreshToken.token);
    LocalStorage.setItem("refresh-expires", refreshToken.expires.toString());
  }

  /**
   * set token & refreshtoken
   * @param token
   * @param refreshToken
   */
  static setCredentials(token: Token, refreshToken: Token) {
    JsonWebToken.setToken(token);
    JsonWebToken.setRefreshToken(refreshToken);
  }

  /**
   * destroy token & refresh token
   */
  static destroyToken() {
    LocalStorage.removeItem("token");
    LocalStorage.removeItem("token-expires");
    LocalStorage.removeItem("refresh-token");
    LocalStorage.removeItem("refresh-expires");
  }

  /**
   * get language
   */
  static getLang() {
    return LocalStorage.getItem("lang");
  }

  /**
   * set language
   * @param lang
   * @param refreshToken
   */
  static setLang(lang: string) {
    LocalStorage.setItem("lang", lang);
  }
}

Create api service

  cd src/app
  ng generate service shared/services/api

api.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { environment } from '../../environments/environment';

@Injectable({
  providedIn: 'root',
})
export class ApiService {
  constructor(public http: HttpClient) {}

  /**
   * throw exception
   * @param error
   */
  private formatErrors = (response: Error): any => {
    // Choose how to show errors to users
    return throwError(response);
  };

  /**
   * call ws via method post
   * @param path
   * @param body
   */
  post(path: string, body: any = {}): Observable<any> {
    return this.http
      .post(`${environment.apiUrl}${path}`, body, { observe: 'response' })
      .pipe(
        map((response: any) => {
          if (response.body['message']) {
            // Choose how to show success messages to users
          }
          return response.body;
        }),
        catchError(this.formatErrors)
      );
  }

  /**
   * call ws via method patch
   * @param path
   * @param body
   */
  patch(path: string, body: any = {}): Observable<any> {
    return this.http
      .patch(`${environment.apiUrl}${path}`, body, { observe: 'response' })
      .pipe(
        map((response: any) => {
          if (response.body['message']) {
            // Choose how to show success messages to users
          }
          return response.body;
        }),
        catchError(this.formatErrors)
      );
  }

  /**
   * call ws via method put
   * @param path
   * @param body
   */
  put(path: string, body: any = {}): Observable<any> {
    return this.http
      .put(`${environment.apiUrl}${path}`, body, { observe: 'response' })
      .pipe(
        map((response: any) => {
          if (response.body['message']) {
            // Choose how to show success messages to users
          }
          return response.body;
        }),
        catchError(this.formatErrors)
      );
  }

  /**
   * call ws via method delete
   * @param path
   */
  delete(path: string): Observable<any> {
    return this.http
      .delete(`${environment.apiUrl}${path}`, { observe: 'response' })
      .pipe(
        map((response: any) => {
          if (response.body['message']) {
            // Choose how to show success messages to users
          }
          return response.body;
        }),
        catchError(this.formatErrors)
      );
  }

  /**
   * call ws via method get
   * @param path
   * @param params
   */
  get(
    path: string,
    params: HttpParams = new HttpParams(),
    responseType?: string
  ): Observable<any> {
    let option: { [key: string]: any } = {};
    if (params) {
      option = { params, observe: 'response' };
    }
    if (responseType) {
      option['responseType'] = responseType as 'json';
      option['observe'] = 'response';
    }

    return this.http
      .get(`${environment.apiUrl}${path}`, option)
      .pipe(map((response: any) => response.body));
  }
}

Create auth Service

Create a new folder services in the src/app/shared folder

  cd src/app
  ng generate service shared/services/auth

auth.service.ts

import { Injectable } from '@angular/core';
import { Login } from '../interfaces/login';
import { Observable } from 'rxjs/internal/Observable';
import { Auth } from '../interfaces/auth';
import { BehaviorSubject, map } from 'rxjs';
import { User } from '../classes/user';
import { JsonWebToken } from '../utils/json-web-token';
import { ApiService } from './api.service';
import { Register } from '../interfaces/register';
import { ForgotPassword } from '../interfaces/forgot-password';
import { ResetPassword } from '../interfaces/reset-password';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  user: BehaviorSubject<User | null> = new BehaviorSubject<User | null>(null);
  isLoggedIn = new BehaviorSubject(false);

  constructor(private apiService: ApiService) {}

  login(credentials: Login): Observable<Auth> {
    return this.apiService.post('auth/login', credentials).pipe(
      map((auth: Auth) => {
        this.user.next(auth.user);
        this.isLoggedIn.next(true);
        JsonWebToken.setCredentials(auth.tokens.access, auth.tokens.refresh);
        return auth;
      })
    );
  }
  register(credentials: Register): Observable<Auth> {
    return this.apiService.post('auth/register', credentials).pipe(
      map((auth: Auth) => {
        this.user.next(auth.user);
        this.isLoggedIn.next(true);
        JsonWebToken.setCredentials(auth.tokens.access, auth.tokens.refresh);
        return auth;
      })
    );
  }
  forgotPassword(body: ForgotPassword): Observable<Response> {
    return this.apiService.post('auth/forgot-password', body);
  }
  resetPassword(body: ResetPassword): Observable<Response> {
    return this.apiService.post('auth/reset-password', body);
  }
}

Update app.config.ts

import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { SharedModule } from './shared/shared.module';
import { HttpClientModule } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [
    importProvidersFrom(HttpClientModule),
    provideRouter(routes),
  ],
};

Update login component

login.component.ts

import { Component } from '@angular/core';
import {
  FormGroup,
  FormControl,
  Validators,
  ReactiveFormsModule,
} from '@angular/forms';
import { Router, RouterLink } from '@angular/router';
import { AuthService } from '../../shared/services/auth.service';
import { Auth } from '../../shared/interfaces/auth';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [ReactiveFormsModule, RouterLink],
  templateUrl: './login.component.html',
  styleUrl: './login.component.scss',
})
export class LoginComponent {
  loginForm = new FormGroup({
    email: new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', Validators.required),
  });
  constructor(private authSerice: AuthService, private router: Router) {}
  onSubmit() {
    const { email, password } = this.loginForm.value;
    if (email && password) {
      this.authSerice.login({ email, password }).subscribe((response: Auth) => {
        if (response.user) {
          this.router.navigate(['/dashboard']);
        }
      });
    }
  }
}

login.component.html

<div
  class="w-full max-w-sm p-4 bg-white border border-gray-200 rounded-lg shadow sm:p-6 md:p-8 dark:bg-gray-800 dark:border-gray-700 m-auto min-h-full"
>
  <div class="sm:mx-auto sm:w-full sm:max-w-sm">
    <h2
      class="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900"
    >
      Sign in to your account
    </h2>
  </div>

  <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
    <form class="space-y-6" [formGroup]="loginForm" (ngSubmit)="onSubmit()">
      <div>
        <label
          for="email"
          class="block text-sm font-medium leading-6 text-gray-900"
          >Email address</label
        >
        <div class="mt-2">
          <input
            id="email"
            name="email"
            type="email"
            formControlName="email"
            class="block w-full rounded-md border-0 p-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
          />
        </div>
      </div>

      <div>
        <div class="flex items-center justify-between">
          <label
            for="password"
            class="block text-sm font-medium leading-6 text-gray-900"
            >Password</label
          >
          <div class="text-sm">
            <a
              [routerLink]="['/auth', 'forgot-password']"
              class="font-semibold text-indigo-600 hover:text-indigo-500"
              >Forgot password?</a
            >
          </div>
        </div>
        <div class="mt-2">
          <input
            id="password"
            name="password"
            type="password"
            formControlName="password"
            class="block w-full rounded-md border-0 p-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
          />
        </div>
      </div>

      <div>
        <button
          [disabled]="!loginForm.valid"
          type="submit"
          class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:opacity-50"
        >
          Sign in
        </button>
      </div>
    </form>

    <p class="mt-10 text-center text-sm text-gray-500">
      Not a member?
      <a
        [routerLink]="['/auth', 'register']"
        class="font-semibold leading-6 text-indigo-600 hover:text-indigo-500"
        >Register</a
      >
    </p>
  </div>
</div>

You can apply the same principles to the other components by using the function defined above in the auth service.

In the next article, We will create the Product pages which will allows authenticated users to create, update, delete and list products.


Stay tuned!


5 views0 comments

Recent Posts

See All
bottom of page