Persisting sign-in state with Angular service

With a barebones UI in place, we need to start building some actual functionality into the application. The forms actually need to submit its contents to something instead of just logging them, and the UI has to have some indication as to whether a user in signed in or signed out. Additionally, the “Profile” page has to be locked down to signed in users only. To accomplish all of this, we’ll need to start adding some persistent state to the client side!

User service

Angular services are a great fit for what we want to do, so let’s make one! I’ll throw it in a \shared\user directory, just to keep track of it.

\src\app\shared\user $ ng g s user
installing service
 create src\app\shared\user\user.service.spec.ts
 create src\app\shared\user\user.service.ts
 WARNING Service is generated but not provided, it must be provided to be used

The CLI has helpfully warned us that this service hasn’t been provided anywhere in the app. As we only ever want a single instance of this service, we’ll provide it in the root of the application – app.module.ts.

// app.module.ts

...
import { HeaderComponent } from './header/header.component';
+ import { UserService } from './shared/user/user.service';
...
- providers: [],
+ providers: [UserService],
...

We’ll flesh it out with some placeholder functions, using local storage to cache a simulated authentication token on the client side. Note that this is one small step above useless – it doesn’t check anything, it doesn’t reach out to the server,  it just basically sets the status to signed in or signed out when told to. We’ll implement this for real shortly.

// user.service.ts

import { Http } from '@angular/http';
import { Injectable } from '@angular/core';

@Injectable()
export class UserService {

  private authToken = 'auth_token';
  private signedIn: boolean = false;

  constructor(private http: Http) {
    this.signedIn = !!localStorage.getItem(this.authToken);
  }

  signIn(email: string, password: string) {
    // This would be where we call out to the server to authenticate
    // We'll use 'token' as a placeholder for now
    localStorage.setItem(this.authToken, 'token');
    this.signedIn = true;
  }

  create(username: string, email: string, password: string) {
    // Obviously this is not what this function will ultimately do
    this.signIn(email, password);
  }

  signOut() {
    localStorage.removeItem(this.authToken);
    this.signedIn = false;
  }

  isSignedIn() {
    return this.signedIn;
  }
}

Allow “signing in”

For the time being, there will be two ways to sign into the app. An existing user can sign in (duh), or a new user can register. This means both the sign-in component and the sign-up component will need access to the user service.

// Both sign-in.component.ts and sign-up.component.ts

...
+ import { UserService } from './../shared/user/user.service';
...
- constructor() { }
+ constructor(private userService: UserService) { }
...

We’ll also connect the form submission to the user service:

// sign-in.component.ts

- onSignIn(email, password) {
-   console.log(this.form);
+ onSignIn() {
+   let formContents = this.form.value;
+   this.userService.signIn(formContents.email, formContents.password);
}
// sign-up.component.ts

onSignUp() {
-  console.log(this.form);
+  let formContents = this.form.value;
+  this.userService.create(formContents.username, formContents.email, formContents.passwords.password)
}

Signing in

This is all well and good, but how do we know if we’re signed in or not? One way to do so would be change the navigation bar depending on the status. That will address a bit of the usability, as well as taking care of visualizing the state. Like with the sign in and sign out components, we’ll get rid of the OnInit at the same time.

// header.component.ts

- import { Component, OnInit } from '@angular/core';
+ import { Component } from '@angular/core';

+ import { UserService } from './../shared/user/user.service';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
- export class HeaderComponent implements OnInit {
+ export class HeaderComponent {

+  constructor(private userService: UserService) { }

-  constructor() { }
+  isSignedIn() {
+    return this.userService.isSignedIn();
+  }

-  ngOnInit() {
+  signOut() {
+    this.userService.signOut();
  }
}

We’ll make the header show the appropriate links when relevant, and add a button in to sign out.

// header.component.html

<nav>
  <a [routerLink]="['/']">Home</a>
  <a [routerLink]="['/profile']">Profile</a>
-  <a [routerLink]="['/sign-in']">Sign In</a>
-  <a [routerLink]="['/sign-up']">Sign Up</a>
+  <a *ngIf="!isSignedIn()" [routerLink]="['/sign-in']">Sign In</a>
+  <a *ngIf="!isSignedIn()" [routerLink]="['/sign-up']">Sign Up</a>
+  <button *ngIf="isSignedIn()" (click)="signOut()" style="float: right;">Sign Out</button>
</nav>

This pretty much works! It’s a bit gross usability wise, but we’re getting there…

signingin

Before signing in, users can sign in and sign up

signout

After signing in, the sign in links disappear and a sign out button appears

So maybe there’s a kink or two to work out… We can improve this all significantly for pretty cheap.

Route away

The app makes a bunch more sense if sent the user to a different page after signing in/signing out. For the naive first pass, we can just send everybody to the “profile” page. At this point, all its accomplishing is not showing the user the “sign in” page right after signing in.

// sign-in.component.ts

+ import { Router } from '@angular/router';
...
-  constructor(private formBuilder: FormBuilder, private userService: UserService) {
+  constructor(private formBuilder: FormBuilder, private userService: UserService, private router: Router) {
...  
  onSignIn() {
    let formContents = this.form.value;
    this.userService.signIn(formContents.email, formContents.password);
+    this.router.navigateByUrl("/profile");
  }

Samesies for the sign-up component.

// sign-up.component.ts

+ import { Router } from '@angular/router';
...
-  constructor(private formBuilder: FormBuilder, private userService: UserService) {
+  constructor(private formBuilder: FormBuilder, private userService: UserService, private router: Router) {
...  
  onSignUp() {
    let formContents = this.form.value;
    this.userService.create(formContents.username, formContents.email, formContents.passwords.password)
+   this.router.navigateByUrl("/profile");
 }

Also dump the user on the home page when they’ve signed out

// header.component.ts

+ import { Router } from '@angular/router';
...
- constructor(private userService: UserService) { }
+ constructor(private userService: UserService, private router: Router) { }
...  
  signOut() {
    this.userService.signOut();
+    this.router.navigateByUrl("/");
  }

Things are pretty much working with the sign in/sign out flow. Unfortunately nothing is respecting whether a user is signed in or signed out. In particular, it was a goal that the profile page would be locked to only signed in users. That’s not happening right now – a signed out user can navigate right back to the profile page, so lets prevent that from happening.

Guarding the profile page

The whole point of signing in is to differentiate access to particular resources. We’re simulating that with the profile page – pretending that’s something that’s only accessible to logged in users. To restrict access, we’ll make use of an Angular guard.

// min-auth/min-auth-client/src/app/shared/authenticated.guard.ts

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';

import { UserService } from './user/user.service';

@Injectable()
export class AuthenticatedGuard implements CanActivate {

  constructor(private userService: UserService, private router: Router) { }

  canActivate() {
    if (!this.userService.isSignedIn()) {
      this.router.navigate(['/sign-in']);
      return false;
    }

    return true;
  }
}

Provide it in the app.module.ts:

// app.module.ts
...
+ import { AuthenticatedGuard } from './shared/authenticated.guard';
...
-   providers: [UserService],
+   providers: [UserService, AuthenticatedGuard],
...

And attach it to the route we want to guard:

// app-routing.module.ts
...
+ import { AuthenticatedGuard } from './shared/authenticated.guard';
...
-  { path: 'profile', component: ProfileComponent }
+  { path: 'profile', component: ProfileComponent, canActivate: [AuthenticatedGuard] }
...

And voila, the profile page is protected. In this case, all logged in users see the same page, which is likely unrealistic for something like a profile page. The important part is that we’ve accomplished what we set out to with the UI portion of this (see the goals in this post):

  • We have somewhere to dump public users -the landing page
  • We have a page that is not accessible to public users, but is accessible to signed in users – the profile page
  • We have a sign up page
  • We have a sign in page
  • We have a UI manifestation as to whether a user is signed in or signed out – the presence/absence of the Sign Out button and the Sign In/Sign Up links

 

That’s it for our extreeeeeemely “minimalist” sign in/sign out UI. This is enough structure to get us moving on the server side aspect of authentication and have something to associate it with. Everything is easier if you can see it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s