Aug 27, 2017

Edit: Update Here! Flat File CMS with Google Sheets, PHP, and Angular 4

In the process of developing an Angular 4 CMS with Flat Files I knew I needed some sort of authentication to enable administrators to login to special areas to edit the content that is within JSON files. In the regard, I may have lied a little bit, as we do still need server side code in order to do proper authentication. You could set up a whole database or use other backends, but I wanted to keep it extremely simple and continue on with the flat file idea by using PHP. PHP can be run on even shared webhosts like Namecheap, whereas .NET and Python typically require something like a VPS set up. Below you can see the full plan for what we will create over a series of posts, starting in this post with the authentication piece which is one of the more difficult aspects:

Overview

The following is a rough outline for what we will cover in this post. Once complete you will have a barebones authentication service with Angular 4 that can be run on any server, even shared hosting.

  1. Server Side Authentication with PHP
  2. Angular4 Auth Service
    • HttpClient
    • Observable
  3. Login Page
  4. Restricted/Unrestricted Components
  5. AuthGuard Routing with CanActivate

Demo

To see this barebones authentication in action, head to http://acostanza.com/php-angular-auth. You should be redirected to /unrestricted/ and see the following content:

If you click the link, it attempts to route you to /restricted/ but sees that you do not have login privileges. It presents the login page to pass credentials to our PHP authentication service.

I included a piece below that shows this form is an Angular form that submits the username and password asynchronously to the PHP authentication service. Login using the credentials you see below:

Hit submit and you are brought to the restricted route at /restricted/:

Pretty simple. Using this barebones example you can have as many restricted routes to administrative components as you’d like that are accessible via a login page.

Server Side Authentication Details

In the past when I made websites I always used PHP sessions to manage login systems. Recently I was reading a lot about JWT and wanted to implement them, but honestly feel like there is zero point in doing so for a small scale application that doesn’t interact with other external APIs that require authentication. Additionally, the idea of relying on Javascript’s localStorage to determine if a user was authenticated felt kind of gross, I prefer pinging the server to ensure a malicious variable hasn’t been stored in localStorage.

  1. users.php
    • Contains JSON encoded user,pass pairs with passwords encoded with PHP’s password_hash()
    • Saved into a PHP variable to ensure no access is granted client-side
  2. auth.php
    • POST(user:string,pass:string)
      • return(user:string,auth:int)
        • if auth == 0, not authenticated
        • if auth == 1, authenticated for admin CMS pages
    • GET()
      • return(user:string OR none, auth:int)
        • same auth rules as above

PHP Files

Put the following into auth.php:

<?php
session_start();

$rawBody = file_get_contents("php://input"); // Read body


if($rawBody != "") { //if post data
require("users.php");
if(sizeof($users) > 0) {
	$user_list = json_decode($users);
}

	$data = json_decode($rawBody);
	//post data from login form
	$user = $data->user;
	$pass = $data->pass;
	$auth = false;
	foreach($user_list as $u) {
		//check username and hashed password
		if($u->user == $user && password_verify($pass,$u->pass)) {
			echo '{"user":"'.$user.'", "auth":"1"}';
			$_SESSION['auth'] = 1;
			$_SESSION['user'] = $user;
			$auth = true;
			break;
		}
	}
	if(!$auth) {
		//if wrong authentication details, output not authenticated
		echo '{"user":"'.$user.'", "auth":"0"}';
		$_SESSION['auth'] = 0;
		$_SESSION['user'] = 'none';
	}
} else {
	//if no post data
	//if not authenticated, output that fact
	if($_SESSION['auth'] == 0) {
		echo '{"user":"none", "auth":"0"}'; 
	} else {
		//if authenticated, output json
		echo '{"user":"'.$_SESSION['user'].'", "auth":"'.$_SESSION['auth'].'"}';
	}
}
?>

This does all of the authentication work on the server side. It opens the users.php file (described below) and grabs the json encoded usernames and hashed passwords that are stored in a PHP variable. I like storing it in a PHP variable because that means the data is not available client side, but can be easily loaded and updated on the server.

Now put the following in users.php:

<?php
$users = '[{"user":"adam","pass":"$2y$10$p7qPMEH2ZcsIWV6TptMCGOmWqa5evX9dnbe.GOvtFOsN1ozEv.ja2"}]';
?>

This has only one user, adam with the hashed  password tacos, as described above in the Demo. You can generate your own JSON object of a user using this PHP code:

<?php
$user['user'] = 'name_here';
$user['pass'] = password_hash("password_here",PASSWORD_DEFAULT);
echo json_encode($user);
?>

Angular 4 AuthService

In order to check if we are authorized, we need an Angular Service that will check the auth.php file using the HttpClient to tell the application if the user is authenticated. The AuthService must be able to POST a username and password and receive a 0 or 1 for authorization and also GET the page without credentials after authentication. You could store a variable in the localStorage but I feel like it is better to have the server indicate whether the user is authenticated rather than the vulnerable front-end memory.

Here is what I have written for my simple AuthService, put it in auth.service.ts:

import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
@Injectable()
export class AuthService {
	constructor(private http: HttpClient) {};
	auth: boolean;
	checkAuth(): Observable<boolean> {
		return this.http.get('auth.php')
			.map(response => {
				console.log(response);
				if(response['auth'] == 1) {
					return true;
				} else {
					return false;
				}
			});
	}
	login(u: string, p: string): Observable<boolean> {
		return this.http.post('auth.php', JSON.stringify({"user": u, "pass":p}))
			.map(response => {
				console.log(response);
				if(response['auth'] == 1) {
					return true;
				} else {
					return false;
				}
			}).catch(() => {
				console.log("Could not login");
				return Observable.of(false);
			});
	}
}

Above you can see two different methods, login(user,pass) and checkAuth(). They both return Observable<boolean> because they implement the aysnchronous HttpClient method. Observables can be mapped, for example the above code maps the JSON response ‘auth’ == 1 to true and else to false. The reason to return the user data is that it may be used in an administrative area and passed on to a local variable, but in this case we do not actually use it.

Restricted/Unrestricted Components

Now that we have an AuthService, let’s create a component that can be accessed by anyone and a component that can only be accessed after a login and authorization check.

Let’s start with the unrestricted component, basically the default one that is created by ng new <project-name>. Put the following in app.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-component',
  templateUrl: './app.component.html'
})
export class AppComponent {
  title = 'app';
}

And the following in app.component.html:

<div class="row">
	<div class="col-xs-1">
	</div>
	<div class="col-xs-10 text-center">Unrestricted component at /unrestricted/</div>
	</div>
<div class="row">
	<div class="col-xs-1">
	</div>
	<div class="col-xs-10 text-center">
		<a [routerLink]="['/restricted']">Access restricted content</a>
	</div>
</div>

This provides the basic unrestricted page with a link to /restricted/ which we will protect via our AuthGuard later. See below for the code for the restricted route, not much is different except the HTML:

In restricted.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'restricted',
  templateUrl: './restricted.component.html'
})
export class RestrictedComponent {
  title = 'app';
}

And in restricted.component.html:

<div class="row">
	<div class="col-xs-1">
	</div>
	<div class="col-xs-10 text-center">Restricted component at /restricted/</div>
	</div>
<div class="row">
	<div class="col-xs-1">
	</div>
	<div class="col-xs-10 text-center">
		<a [routerLink]="['/unrestricted']">Access unrestricted content</a>
	</div>
</div>

As you can see, there isn’t anything super exciting in this component. But you could make this be an administrative console, or restricted components could also have areas to edit content from either a flat file storage solution or a database.

Login Page

We also need a login page now that we can redirect an unauthorized user to in order to put credentials in to verify against the server. For this example I used a simple bootstrap login that I found. I modified the HTML code to make it work with Angular’s Form module using a template driven model.

Save this into login.html:

<div class="row">
	<div class="col-sm-3"></div>
    <div class="col-sm-6">
        <h1 class="text-center login-title">Sign in to continue</h1>
        <div class="account-wall">
            <img class="profile-img" src="https://lh5.googleusercontent.com/-b0-k99FZlyE/AAAAAAAAAAI/AAAAAAAAAAA/eu7opA4byxI/photo.jpg?sz=120" alt="">
            <form class="form-signin" #f="ngForm" (ngSubmit)="onSubmit(f.value)">
				<input type="text" class="form-control" placeholder="Email" required autofocus name="user" ngModel>
				<input type="password" class="form-control" placeholder="Password" required name="pass" ngModel>
				<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
			</form>
			<pre>{{f.value | json}}</pre>
		</div>
	</div>
</div>

Make sure you also save the css code from here into login.css

Component with AuthService

Now that the basic form is set up, we need to set up the login component which injects the AuthService to check user credentials against the server. When the login page is encountered, the component should check if the user is already authorized and if so redirect to the restricted page (or administrative area). If the user is unauthorized the login form is displayed and the onSubmit method from the template-driven form is handled to post the user credentials against the PHP auth.php file.

Put the following code into admin.login.ts:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from './auth.service';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/catch';

@Component({
  selector: 'admin-login',
  templateUrl: 'login.html',
  styleUrls:['login.css'],
  providers: [AuthService]
})
export class AdminLogin implements OnInit {
  constructor(private authService: AuthService, private router: Router) {};
    //we want to actually subscribe to the boolean of the observable
	onSubmit(form: any): void {
		console.log(form.user);
		this.authService.login(form.user, form.pass).subscribe(auth =>{
			if(auth) {
				this.router.navigate(['/restricted']);
			}
		});
	}
	ngOnInit(): void {
		this.authService.checkAuth().subscribe(auth =>{
			if(auth) {
				this.router.navigate(['/restricted']);
			}
		});
	}
}

As you see, on initialization this component checks the PHP auth page without credentials to see if the login page is necessary. On submission of the form the username and password are posted to the auth page as well and redirection occurs if authorized. Note that the login actually subscribes to the Observable<boolean> provided by AuthService, because we want to get the boolean result from the Observable by subscribing to it, and only rerouting to the restricted area if the result of the Observable is true. If the result of the observable is false, we of course stay on the login page with no additional navigation.

AuthGuard Routing with CanActivate

Now that we have the components it is time to set up the routes. The previous components had routerLink which refers to a specific path. The routing for this is all done in app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import {HttpClientModule} from '@angular/common/http';
import {Routes, RouterModule, Router, CanActivate, CanActivateChild} from "@angular/router";
import { FormsModule } from '@angular/forms';

import { AppIndex } from './app.index';
import { AuthGuard } from './auth.guard';
import {AuthService} from "./auth.service";
import {AdminLogin} from "./admin.login";
import { AppComponent } from './app.component';
import { RestrictedComponent} from './restricted.component';

const routes: Routes = [
  {path: '', redirectTo: 'unrestricted', pathMatch: 'full'},
  {path: 'unrestricted', component: AppComponent},
  {path: 'login', component:AdminLogin},
  {path: 'restricted', component:RestrictedComponent,
  canActivate: [AuthGuard],
  canActivateChild:[AuthGuard],
  children: [
  {path:'alsoRestricted', component:RestrictedComponent}
  ]},
  {path: '**', component: AppComponent}
];

@NgModule({
  declarations: [
    AppComponent,
	AdminLogin,
	RestrictedComponent,
	AppIndex
  ],
  imports: [
    BrowserModule,
	HttpClientModule,
	FormsModule,
	RouterModule.forRoot(routes,{useHash:true})
  ],
  providers: [AuthGuard, AuthService],
  bootstrap: [AppIndex]
})
export class AppModule { }

Most of this is just like a typical module. It is the following portion of code where the Routes are defined including the AuthGuard for restricted routes:

const routes: Routes = [
  {path: '', redirectTo: 'unrestricted', pathMatch: 'full'},
  {path: 'unrestricted', component: AppComponent},
  {path: 'login', component:AdminLogin},
  {path: 'restricted', component:RestrictedComponent,
  canActivate: [AuthGuard],
  canActivateChild:[AuthGuard],
  children: [
  {path:'alsoRestricted', component:RestrictedComponent}
  ]},
  {path: '**', component: AppComponent}
];

The work is done through the canActivate property which calls AuthGuard. You can actually have multiple different AuthGuards for different routes. I also have a canActivateChild route which just links to the same component. I actually read a really interesting article by Netanel Basal about how you can implement componentless routes which essentially enables you to only have to list the canActivateChild property once and then have all protected routes under the parent route. Pretty cool stuff!

Index Page

The very last thing we need to run this barebones authentication with PHP and Angular 4 is an index component.

The following goes in app.index.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: 'index.html',
  providers: []
})
export class AppIndex {
  
}

And the following into index.html:

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

<div class="container-fluid" style="max-width:960px;">
<router-outlet></router-outlet>
</div>

And we are in business! One again, access the Demo here. You may have noticed there isn’t a logout, this can easily be done with an asynchronous request to a logout.php page that simply has <?php session_destroy(); ?> within it, but I didn’t really have a particular need for it here.

Full Code

You can find the full source code on GitHub here.