March, 5 2020

Tutorial: Angular User Registration & Login

In this tutorial, I will show you how to use Busywork to create:

  • User Sign Up
  • User Sign In
  • User Sign Out

Step 1: Setting Up Busywork

Sign Up with Busywork

Go to https://busywork.co/sign-up/ and register. It has a free plan that contains all the features that we need for this tutorial.

Once signed in, go to Builds where you'll see all of the backend functions we need for this tutorial.

User authentication functions

The already created functions serve as simple examples of what you can make in Busywork. Feel free to discover other and more complex flow that for instance allow you to send a welcome email, add a user to your CRM system and ...

Step 2: Setup an Angular 8 Project

To get started we need to create a new Angular project. We'll be using the Angular CLI tool to create the new project. Run the following command:

ng new busywork-user-authentication

Which will prompt you about routing and which styling you want to use:

? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS   [ https://sass-lang.com/documentation/syntax#scss                ]

Now we have to change the directory to our newly created Angular project.

cd busywork-user-authentication

For this tutorial, we'll need to create a few components:

  • Sign Up Component
  • Sign In Component
  • Profile Component

Create the components by running the following commands.

ng g c user/sign-up
ng g c user/sign-in
ng g c user/profile

Finally, we will be using Bootstrap 4 to provide some styling to our components and elements. Add it to your project by running this command.

npm i bootstrap

Add the Bootstrap style to your project by adding the following line in the angular.json file.

"styles": [          
    "node_modules/bootstrap/dist/css/bootstrap.min.css",
    "src/styles.scss"
]

Now you are ready to start your app.

ng serve

Step 3: User Authentication Service

We now create a service that we will use as an abstraction for our Busywork functions. Inside the user folder we create a service called authentication.

ng g s user/authentication

We are consuming the Busywork API using AJAX calls, so we need to make the HttpClientModule available within the service. In app.module.ts import the module.

import { HttpClientModule } from "@angular/common/http";

And add it to the imports section of the module declaration.

imports: [
  .
  .
  HttpClientModule,
  .
  .
],

First, we initialize some variables that we'll be using throughout the methods in the service.

// your busywork workspace URL
workspace = 'http://gracious-lamport-58c4db.busywork.ai';
// JSON header
jsonHeader = {headers: new HttpHeaders({'Content-Type':  'application/json'})};

Next, we create methods for:

  • Getting the users access token
  • Signing up the user
  • Signing in the user
  • Signing out the user
  • Authenticating the user

Get Access Token

We'll be using the local storage for storing the user's access token once obtained. We need to access the token later, so we make a handy helper function inside the authentication service.

getAccessToken(){
  return localStorage.getItem('user_access_token');
}

Sign Up User

To sign up a user we need to invoke the appropriate Busywork function: this.workspace + '/sample-sign-up'. Inside Busywork you can see the required arguments of the function. We need a username and password to sign up a user.

signUp(username:string, password:string){
  return this.http.post(
    this.workspace + '/sample-sign-up',
    // body content - required args can be seen in the Busywork function
    {
      'username': username,
      'password': password
    },
    this.jsonHeader
  );
}

When we get a response from the Busywork API we can choose what to do if the user has been signed up or if the sign up failed.

Sign In User

Signing in the user is done in the same way - we send username and password to the Busywork function. But with this function, we want to store the user's access token if the sign-in is successful.

signIn(username:string, password:string){
  return this.http.post(
    this.workspace + '/sample-sign-in',
    // body args
    {
      'username': username,
      'password': password
    },
    this.jsonHeader
  ).subscribe(
    (result:any) => {
      // save the access token in local storage
      localStorage.setItem('user_access_token', result['access_token']);
      // save the user id in local storage
      localStorage.setItem('user_id', result['user_id']);
      // change route to the profile component
      this.router.navigate(['profile']);
    }
  )
}

Finally, if the user has signed in we redirect the user to the profile page.

Sign Out User

For sign-out, we don't need to send any data to the function. The function only needs the access token, which will be included in the request header.

signOut(){
  return this.http.post(
    this.workspace + '/sample-sign-out',
    {},
    this.jsonHeader
  ).subscribe(
    (result:any) => {
      this.router.navigate(['sign-in']);
    }
  )
}

Authenticating the User

This service method is used to check with the Busywork API if the user's access token is valid. A user's access token is invalid if it has either expired or been revoked on sign-out.

authenticate(){
  return this.http.post(
    this.workspace + '/sample-authenticate',
    {},
    this.jsonHeader
  );
}

Step 4: Setting the Access Token

All authentication in Busywork uses JWT tokens to determine whether a user is signed in or not. This also allows you to gate content and query data belonging to this particular user.

Using JWT tokens requires us to pass this token with every request we make to Busywork. We pass the token in the header of the request. We could set it manually every time we make a request or use a HttpInterceptor that can do it for us.

In the user folder create a new Typescript file and call it user.request.interceptor.ts. This will hold the HttpInterceptor.

import {Injectable} from "@angular/core";
import {HttpInterceptor, HttpRequest, HttpHandler} from "@angular/common/http";
import {AuthenticationService} from "./authentication.service";
import {isNullOrUndefined} from "util";

@Injectable()
export class UserRequestInterceptor implements HttpInterceptor {
  constructor(private authService: AuthenticationService){

  }
  
  // interceptor transforms an outgoing request before passing it to the next interceptor
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // get the users access token using our helper function
    const accessToken = this.authService.getAccessToken();
    
    // in case it isn't set
    if(isNullOrUndefined(accessToken))
      return next.handle(req);
    
    // set the header
    req = req.clone({
      setHeaders: {
        Authorization: "Bearer " + accessToken
      }
    });
    return next.handle(req);
  }
}

To use the HttpInterceptor we have to provide it in our app module.

@NgModule({
  declarations: [
    AppComponent,
    SignUpComponent,
    SignInComponent,
    ProfileComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ReactiveFormsModule,
    FormsModule,
    HttpClientModule
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: UserRequestInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})

Step 5: Guarding Content

Implementing user authentication means that we most likely want to guard private content from the public. We will guard certain routes in our app by placing a guard. This guard uses a Busywork function to check if an access token is valid.

We create the guard using the following command:

ng g guard user/auth

In the auth.guard.ts file, that you've just created, the class AuthGuard implements an interface CanActivate. It requires us to return one of the following types:

Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree

The response from the Busywork function is a JSON object. In order to implement the interface correctly, we need to map the response we get from Busywork.

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
  return this.authService.authenticate().pipe(
    map(
      active => {
        return active['status'] == 'success';
      }
    )
  );
}

The canActivate function is used to determine if a route can be accessed by a user.

Now we can set up the routes of the app, in app-routing.module.ts, and protect the profile using our AuthGuard.

const routes: Routes = [
  {path: '', redirectTo: '/sign-in', pathMatch: 'full'},
  {path: 'sign-in', component: SignInComponent},
  {path: 'sign-up', component: SignUpComponent},
  {path: 'profile', component: ProfileComponent, canActivate: [AuthGuard]}
];

Step 6: Sign Up Component

In the sign-up component, we need to create a sign-up form. This is a very simple example, so we only want a username and password. We'll use Reactive Forms to build the form validation. This requires us to import the following modules:

import { ReactiveFormsModule, FormsModule } from "@angular/forms";

And adding them to the import section of the module declaration in the app.module.ts file.

imports: [
  BrowserModule,
  AppRoutingModule,
  ReactiveFormsModule,
  FormsModule,
  HttpClientModule
]

In the sign-up component, we will first create a form group with a username and password field.

form: FormGroup;
constructor(private fb: FormBuilder, private authService: AuthenticationService, private router: Router) {
  this.form = this.fb.group({
    username: new FormControl('', Validators.compose([Validators.required, Validators.minLength(3)])),
    password: new FormControl('', Validators.compose([Validators.required, Validators.minLength(6)]))
  });
}

Next, we create a method that is called when the form is submitted. The method will use the Authentication Service to send a request to your Busywork API.

signUpUser(){
  this.authService.signUp(
    this.form.get('username').value,
    this.form.get('password').value
  ).subscribe(
    (result:any) => {
      if(result['status'] == 'success'){
        this.form.reset();
        this.router.navigate(['sign-in']);
      }
    }
  )
}

The HTML part of the component looks like this:

<div class="container">
  <div class="row">
    <div class="col-12">
      <h1>Sign Up</h1>
    </div>
    <div class="col-12">
      <form [formGroup]="form" (ngSubmit)="signUpUser()">
        <div class="form-group">
          <label>Username</label>
          <input type="text" class="form-control" formControlName="username" placeholder="Enter a username" required>
        </div>
        <div class="form-group">
          <label>Password</label>
          <input type="password" class="form-control" formControlName="password" placeholder="Enter a password" required>
        </div>
        <button type="submit" class="btn btn-success">Sign Up</button>
      </form>
    </div>
  </div>
</div>

Step 7: Sign In Component

The sign-in component is very similar to the sign-in component. The contents of sign-in.component.ts is:

import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms";
import {AuthenticationService} from "../authentication.service";

@Component({
  selector: 'app-sign-in',
  templateUrl: './sign-in.component.html',
  styleUrls: ['./sign-in.component.scss']
})
export class SignInComponent implements OnInit {

  form: FormGroup;
  constructor(private fb: FormBuilder, private authService: AuthenticationService) {
    this.form = this.fb.group({
      username: new FormControl('', Validators.required),
      password: new FormControl('', Validators.required)
    });
  }

  ngOnInit() {
  }

  private signInUser(){
    this.authService.signIn(
      this.form.get('username').value,
      this.form.get('password').value
    );
  }

}

And sign-in.component.html:

<div class="container">
  <div class="row">
    <div class="col-12">
      <h1>Sign In</h1>
    </div>
    <div class="col-12">
      <form [formGroup]="form" (ngSubmit)="signInUser()">
        <div class="form-group">
          <label>Username</label>
          <input type="text" class="form-control" formControlName="username" placeholder="Enter a username" required>
        </div>
        <div class="form-group">
          <label>Password</label>
          <input type="password" class="form-control" formControlName="password" placeholder="Enter a password" required>
        </div>
        <button type="submit" class="btn btn-success">Sign In</button>
      </form>
    </div>
  </div>
</div>

Step 8: Profile

Now users are able to both sign-up and sign-in, meaning they can access the Profile-page. We haven't collected any information besides the username and password of the user - so we don't have that much to display. But we can allow the user to sign out again.

In profile.component.ts put:

import { Component, OnInit } from '@angular/core';
import {AuthenticationService} from "../authentication.service";

@Component({
  selector: 'app-profile',
  templateUrl: './profile.component.html',
  styleUrls: ['./profile.component.scss']
})
export class ProfileComponent implements OnInit {

  constructor(private authService: AuthenticationService) { }

  ngOnInit() {
  }
  
  signOutUser(){
    this.authService.signOut();
  }

}

And in profile.component.html:

<div class="container">
  <div class="row">
    <div class="col-12">
      <h1>You're in!</h1>
      <button type="button" class="btn btn-danger" (click)="signOutUser()">
        Click here to sign-out
      </button>
    </div>
  </div>
</div>

Conclusion

This concludes this entry-level tutorial on how you can implement user authentication into your app, using Busywork as a backend. In this tutorial, I've shown a minimal example. In Busywork you'll be able to expand the sign-up process to send welcome emails and add a user to your CRM system.

I'm going to post more tutorials on how you can build the backend of your web app using Busywork. Please let me know what topics you would like me to cover.