Subscribe

Angular authenticating users from an API

✍️

Adding a login to our Angular app, something most applications will need.

23 Oct, 2020 · 10 min read

This article will explore how to add a user service and log in to an Angular application.

We will have a login service, which will call an API (fake) and return a token.

The result will look like this.

Adding login to our Angular application

Creating a user model

Let’s start by defining a user model. In our case, we only store an email and a token.

Open your favorite terminal and run the following command.

ng generate class models/User --type=model

This will generate a user.model.ts file in the models’ folder.

Let’s change this file to reflect our model.

export class User {
    email: string;
    token?: string;
}

You can enhance this model to reflect an actual user. Since we are using a fake endpoint, we only get the email and token back.

Modifying our environment

One fantastic element of Angular is that it comes with environment files. We can keep track of environment-specific variables.

Our API endpoint is going to be one of those.

Our local, test, and production servers usually have different endpoints.

Open your environment/environment.ts file and make it look like this.

export const environment = {
  production: false,
  apiUrl: 'https://reqres.in/',
};

Creating the auth service

Ok, if we have our model, let’s go on to the service. The service will handle the login, user state, and logout functions.

First, we’ll generate this service in the terminal.

ng generate service services/Auth

This will create an auth.service.ts file in the services folder.

Let’s start by defining our variables.

private userSubject: BehaviorSubject<User>;
public user: Observable<User>;

We are using Subjects and observables to store our user object. This way, we can quickly notify other components of changes in this variable.

Next, we need to define our construct.

constructor(private http: HttpClient, private router: Router) {
  this.userSubject = new BehaviorSubject<User>(
    JSON.parse(localStorage.getItem('currentUser'))
  );
  this.user = this.userSubject.asObservable();
}

We are loading the Angular HttpClient and Router and subscribe to our userSubject to load whatever user object we have in our local storage.

Then we return the current user as an observable so it will be notified on every change.

Next, we’ll introduce a custom getter that will make it easy for other components to quickly get the value of the currently logged-in user without subscribing to our observable.

public get userValue(): User {
  return this.userSubject.value;
}

Note: You can see this as a once of data return.

Now let’s make our login function.

login(username: string, password: string) {
return this.http
  .post<any>(`${environment.apiUrl}/api/login`, { username, password })
  .pipe(
    map(({token}) => {
      let user: User = {
        email: username,
        token: token,
      };
      localStorage.setItem('currentUser', JSON.stringify(user));
      this.userSubject.next(user);
      return user;
    })
  );
}

We pass the username and password to this function as strings, then make a POST call to our defined apiUrl and call the api/login endpoint. Here we pass the username and password variables.

Next, we use the pipe and map methods to return the data.

The API returns only a token, so let’s create a new user object with the username and token.

We then set the local storage to contain this token.

Next, we tell the userSubject we received a new value for the user observable.

And return the user object in this function.

Now onto our logout function

logout() {
  localStorage.removeItem('currentUser');
  this.userSubject.next(null);
}

The logout is as simple as removing the currentUser local storage object and sending a null object to our userSubject subject.

The entire file will look like this:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { BehaviorSubject, Observable } from 'rxjs';
import { Router } from '@angular/router';
import { User } from '../models/user.model';
import { environment } from '../../environments/environment';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  private userSubject: BehaviorSubject<User>;
  public user: Observable<User>;

  constructor(private http: HttpClient, private router: Router) {
    this.userSubject = new BehaviorSubject<User>(
      JSON.parse(localStorage.getItem('currentUser'))
    );
    this.user = this.userSubject.asObservable();
  }

  public get userValue(): User {
    return this.userSubject.value;
  }

  login(username: string, password: string) {
    return this.http
      .post<any>(`${environment.apiUrl}/api/login`, { username, password })
      .pipe(
        map(({token}) => {
          let user: User = {
            email: username,
            token: token,
          };
          localStorage.setItem('currentUser', JSON.stringify(user));
          this.userSubject.next(user);
          return user;
        })
      );
  }

  logout() {
    localStorage.removeItem('currentUser');
    this.userSubject.next(null);
  }
}

So, technically, we can log in users and store the user object in local storage, but we have no way to call this function?

Let’s also add the HttpModule to our app.module.ts.

@NgModule({
  declarations: [
    // All declarations
  ],
  imports: [
    // Other imports
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})

Adding a login page

Let’s add a login page, redirecting us to another page where we can see our user object.

Start by generating the login page.

ng generate component Login

The content will look like this.

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';
import { AuthService } from '../services/auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss'],
})
export class LoginComponent implements OnInit {
  loginForm: FormGroup;
  error = '';

  constructor(
    private formBuilder: FormBuilder,
    private router: Router,
    private authenticationService: AuthService
  ) {}

  ngOnInit() {
    this.loginForm = this.formBuilder.group({
      username: ['', Validators.required],
      password: ['', Validators.required],
    });
  }

  get f() {
    return this.loginForm.controls;
  }

  onSubmit() {
    if (this.loginForm.invalid) {
      return;
    }

    this.authenticationService
      .login(this.f.username.value, this.f.password.value)
      .pipe(first())
      .subscribe({
        next: () => {
          this.router.navigate(['/home']);
        },
        error: (error) => {
          this.error = error;
        },
      });
  }
}

We are using the form we learned in the article about Angular Reactive forms.

Then we call our authenticationService once we call the onSubmit function.

This will send the form’s username and password.

If we then get something back, we navigate to the home URL. If not, we will display whatever the error was.

The HTML for this, based on Tailwind CSS.

<form
  [formGroup]="loginForm"
  (ngSubmit)="onSubmit()"
  class="px-8 pt-6 pb-8 mb-4 bg-white rounded shadow-md"
>
  <div class="mb-4">
    Username: [email protected]<br />
    Password: cityslicka
  </div>
  <div class="form-group">
    <label class="block mb-2 text-sm font-bold text-gray-700" for="username"
      >Username</label
    >
    <input
      type="text"
      formControlName="username"
      class="w-full px-3 py-2 leading-tight text-gray-700 border rounded shadow appearance-none focus:outline-none focus:shadow-outline"
    />
  </div>
  <div class="form-group">
    <label
      class="block mt-2 mb-2 text-sm font-bold text-gray-700"
      for="password"
      >Password</label
    >
    <input
      type="password"
      formControlName="password"
      class="w-full px-3 py-2 leading-tight text-gray-700 border rounded shadow appearance-none focus:outline-none focus:shadow-outline"
    />
  </div>
  <button
    [disabled]="!loginForm.valid"
    class="px-4 py-2 mt-4 font-bold text-white bg-blue-500 rounded hover:bg-blue-700 focus:outline-none focus:shadow-outline"
  >
    Login
  </button>
  <div class="mt-2" *ngIf="error">* Error: {{ error.message }}</div>
</form>

Let’s add this route to our app-routing.module.ts file.

const routes: Routes = [
  {
    path: 'welcome',
    component: WelcomeComponent,
    children: [{ path: 'about', component: AboutComponent }],
  },
  {
    path: 'second',
    children: [
      { path: '', component: SecondComponent },
      { path: 'child', component: ChildComponent },
    ],
  },
  { path: 'login', component: LoginComponent },
  { path: '', redirectTo: '/welcome', pathMatch: 'full' },
  { path: '**', component: NotFoundComponent },
];

Creating the home route

We now want to redirect people to our home route and show our logged-in user details.

Let’s generate the home component.

ng generate component Home

The file will look like this.

import { Component } from '@angular/core';
import { User } from '../models/user.model';
import { AuthService } from '../services/auth.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent {
  currentUser: User;

  constructor(private authenticationService: AuthService
) {
  this.authenticationService.user.subscribe(user => this.currentUser = user);
 }

}

As you can see, we are loading our authService and subscribing to the user object. So once the user object changes, this function will update the currentUser object in this component.

Then in our HTML, we return the user object.

<p>home works!</p>
<hr />
{{ currentUser | json }}

Now let’s also add this route to the routing file.

const routes: Routes = [
  {
    path: 'welcome',
    component: WelcomeComponent,
    children: [{ path: 'about', component: AboutComponent }],
  },
  {
    path: 'second',
    children: [
      { path: '', component: SecondComponent },
      { path: 'child', component: ChildComponent },
    ],
  },
  { path: 'login', component: LoginComponent },
  { path: 'home', component: HomeComponent },
  { path: '', redirectTo: '/welcome', pathMatch: 'full' },
  { path: '**', component: NotFoundComponent },
];

Excellent, we can now login as a user, store it in local storage, and see who is logged in on our homepage!

You can find the complete project code on GitHub.

Thank you for reading, and let’s connect!

Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter

Spread the knowledge with fellow developers on Twitter
Tweet this tip
Powered by Webmentions - Learn more

Read next 📖

Angular dynamic classes using ngClass

31 Mar, 2021 · 2 min read

Angular dynamic classes using ngClass

Angular dynamically change form validators

30 Mar, 2021 · 3 min read

Angular dynamically change form validators

Join 2099 devs and subscribe to my newsletter