User Authentication with Laravel 8.0, Angular 11.0

In this tutorial we will see how to implement user authentication. For the given lesson we will need.

  • Angular 11
  • Laravel 8
  • Postman

Create default Laravel and Angular projects and serve them.

php artisan serve
ng serve

in Angular:

Create login component with

ng g c login

Also create routing

Add some basic html to your login component

//login.component.html
<h2>Please, Log in</h2>
<form>
<input type="email" placeholder="email"> <br>
<input type="password" placeholder="password"> <br>
<button>Log in</button>
</form>

Create secure component, to which we will have access when we are logged in:

ng g c secure

And add it to routes, also change route for login component:

// src/app/app.module.ts
const routes = [
{path: 'login', component: LoginComponent},
{path: 'secure', component: SecureComponent}
];
//routes/api.php
Route::get('/', function() {
return 'hello world';
});

We can check for ‘hello world’ message in the browser. But it will be loaded in:

http://127.0.0.1:8000/api

But we want to display it in:

http://127.0.0.1:8000/

That is why we want to modify RouteServiceProvider.php and remove the prefix ‘api’, and comment out middleware ‘web’ like in the image:

After this you should see ‘hello world’ message in the browser in the given port:

http://127.0.0.1:8000/

Next step is creating a UserController, which will display the message. Type in terminal:

php artisan make:controller UserController

It will create a file in app/Http/Controllers/UserController.php

To make sure that our controller works return a ‘hello world’ message:

// app/Http/Controllers/UserController.php
class UserController extends Controller
{
public function index() {
return 'hello world';
}
}

Now modify the route in api.php and make it call the UserController’s index()

Route::get('/', '\App\Http\Controllers\UserController@index');

At this point you should be able to display ‘hello world’ message in the browser.

Modify schema in create_user_table.php:

// database/migrations/2014_10_12_000000_create_users_table.php
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('first_name');
$table->string('last_name');
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}

Set up your database in .env file:

// .env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=authapp2
DB_USERNAME=root
DB_PASSWORD=

After, in the terminal run migrate command:

php artisan migrate

Check your databese and make sure that new tables were created.

We are ready to fill User table with fake data, run the given command in the terminal:

php artisan make:seeder UserSeeder

Call factory function>

// database/seeders/UserSeeder.php
use App\Models\User;

class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
User::factory()
->times(50)
->create();
}
}

Change column names in UserFactory.php:

// database/factories/UserFactory.php
public function definition()
{
return [
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'email' => $this->faker->unique()->safeEmail,
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
}

Fill the User Table with fake data calling the command in the terminal:

php artisan db:seed --class=UserSeeder

Done! Your database is filled with fifty fake profiles!

You can display all users changing index function in UserController.php:

// app/Http/Controllers/UserController.php
use App\Models\User;

class UserController extends Controller
{
public function index() {
return User::all();
}
}

For implementing authentication, we are going to use laravel passport package:

composer require laravel/passport

First we need to create passport tables, so run in terminal:

php artisan migrate

You will see, that in the database many new tables were created

Next, we are going to create some IDs and secret codes with passport. Type in the terminal:

php artisan passport:install

And it should print out a messages similar to:

Client ID: 1
Client secret: 05uUZtQph9UNdeVqqbrRu1BT4s56EDBhlaLPdltV
Password grant client created successfully.
Client ID: 2
Client secret: LYmFJusiC5doqlM5dHGN3Efb0eSGmcPWJZw0Fkqk

With all this information we are going back to .env file and at the and of the file add the lines where the client secret is from the previous message:

// .env
CLIENT_1=05uUZtQph9UNdeVqqbrRu1BT4s56EDBhlaLPdltV
CLIENT_2=LYmFJusiC5doqlM5dHGN3Efb0eSGmcPWJZw0Fkqk

Now in user model in use statement add HasApiTokens and modify the $fillable array:

// app/Models/User.php
use HasFactory, Notifiable, HasApiTokens;

/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'first_name',
'last_name',
'email',
'password'
];

Let’s go to AuthServiceProvide.php and change some code in boot function:

// app/Providers/AuthServiceProvider.php
public function boot()
{
$this->registerPolicies();
Passport::routes();
}

Now you can check that you have a lot of routes with oauth name:

php artisan route:list

We are almost done at this point. Go at config/auth.php and in api key change ‘driver’ => ‘passport’:

// config/auth.php
'api' => [
'driver' => 'passport', // this line
'provider' => 'users',
'hash' => false,
],

So, let’s try to log in.

Go to Postman and type in next parameters where

  • Email is an email from the database;
  • Password the one from the factory, in my case ‘password’
  • Client secret from .env file:

Click send and we should receive a response with an access token:

Before everything we need to configure the login form and for that purpose we will need the ReactiveFormsModule. Import it in app module:

// src/app/app.module.ts
imports: [
BrowserModule,
RouterModule.forRoot(routes),
ReactiveFormsModule
],

Adding Form Builder into Login Component:

// app/login/login.component.ts
export class LoginComponent implements OnInit {
form: FormGroup;
constructor(private fb: FormBuilder) { }

ngOnInit(): void {
this.form = this.fb.group({
email: '',
password: ''
});
}
}

Attaching form group and form controls to the html template and adding a submit event to the form:

// app/login/login.component.html
<h2>Please, Log in</h2>
<form [formGroup]="form" (submit)="onSubmit()">
<input type="email" formControlName="email" placeholder="email"> <br>
<input type="password" formControlName="password" placeholder="password"> <br>
<button>Log in</button>
</form>

We are ready to do HTTP request to the server. Import Http Client Module:

// src/app/app.module.ts
imports: [
BrowserModule,
RouterModule.forRoot(routes),
ReactiveFormsModule,
HttpClientModule
],

Move back to login.component.ts and instantiate HTTP client in the constructor. Also declare your submit function where your are making http request with all same headers like in the request we have made in Postman:

export class LoginComponent implements OnInit {
form: FormGroup;
constructor(private fb: FormBuilder, private http: HttpClient) { }

ngOnInit(): void {
this.form = this.fb.group({
email: '',
password: ''
});
}

onSubmit(): void {
const formData = this.form.getRawValue();
const data = {
username: formData.email,
password: formData.password,
grant_type: 'password',
client_id: 2,
client_secret: 'LYmFJusiC5doqlM5dHGN3Efb0eSGmcPWJZw0Fkqk',
scope: '*'
};

this.http.post('http://localhost:8000/oauth/token', data).subscribe(
result => {
console.log('success');
console.log(result);
},
error => {
console.log('error');
console.log(error);
}
);
}
}

If you have done everything correctly you should have the CORS error when you send a request. You can check it in the browser console:

Let’s solve this problem with CORS error. First install the middleware that handles it.

Go to your laravel project and in terminal write:

composer require fruitcake/laravel-cors

You may want check the documentation for given CORS middleware.

In Kernel.php, in $middleware array add these configurations,

\App\Http\Middleware\TrimStrings::class

and comment out:

// \App\Http\Middleware\TrustHosts::class,

After changing these two lines it will look like:

// app/Http/Kernel.php
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class, // this line
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\Fruitcake\Cors\HandleCors::class, // this line
];

We are not done yet, it is needed to generate a cors.php file where we can configure stuff. Execute:

php artisan vendor:publish --tag=”cors”

Then go to config/cors.php:

// config/cors.php
return [

'paths' => ['/*', 'sanctum/csrf-cookie'],

'allowed_methods' => ['*'],

'allowed_origins' => ['*'],

'allowed_origins_patterns' => [],

'allowed_headers' => ['*'],

'exposed_headers' => [],

'max_age' => 0,

'supports_credentials' => false,

];

Try to log in through the angular form with an email from the database. If you have done everything successfully, you should get, in the browser console, a request with an access token:

Open your laravel project and go to api.php and create a new route for authenticated users:

// routes/api.php
Route::get('/user', '\App\Http\Controllers\UserController@user')->middleware('auth:api');

Create a user function in UserController.php, that will return requested user:

// app/Http/Controllers/UserController.php
class UserController extends Controller
{
public function index() {
return User::all();
}

public function user(Request $request) {
return $request->user();
}
}

Go to app/Http/Middleware directory and delete Authenticate.php file.

Open Kernel.php and in $routeMiddleware change just ‘auth’ field:

// app/Http/Kernel.php
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
...
.];

Try to get user data in Postman. First make a normal post request to http://localhost:8000/oauth/token

Request in Postman

Copy access token from the response and open new tab for new http request in Postman. Send it to http://localhost:8000/user adress. Introduce authorization header with its content ‘Bearer acess_token from the response’ and send the request:

If you have done everything right, you will get user’s data:

Try to get user’s data from angular. Open your login component. Declare router in the constructor and in submit function store access token in angular localStorage.

Make sure that you are using your client secret code from .env file

// app/login/login.component.ts
export class LoginComponent implements OnInit {
form: FormGroup;
constructor(
private fb: FormBuilder,
private http: HttpClient,
private router: Router
) { }

ngOnInit(): void {
this.form = this.fb.group({
email: '',
password: ''
});
}

onSubmit(): void {
const formData = this.form.getRawValue();
const data = {
username: formData.email,
password: formData.password,
grant_type: 'password',
client_id: 2,
client_secret: 'LYmFJusiC5doqlM5dHGN3Efb0eSGmcPWJZw0Fkqk',
scope: '*'
};

this.http.post('http://localhost:8000/oauth/token', data).subscribe(
(result: any) => {
localStorage.setItem('token', result.access_token);
this.router.navigate(['/secure']);
},
error => {
console.log('error');
console.log(error);
}
);
}
}

In secure component:

// app/secure/secure.component.ts
export class SecureComponent implements OnInit {

constructor(private http: HttpClient) { }

ngOnInit(): void {
const headers = new HttpHeaders({
Authorization: `Bearer ${localStorage.getItem('token')}`
});
console.log(localStorage.getItem('token'));
this.http.get('http://localhost:8000/user', {headers}).subscribe(
result => console.log(result)
);
}
}

You can test it in the browser. Try to log in with a fake email from the database. If you are successful, angular will redirect you to /secure and you will be able to see user data in the console:

Let’s show it with html. First declare user variable in secure.ts and assign it to http response:

// app/secure/secure.component.ts
export class SecureComponent implements OnInit {
user;

constructor(private http: HttpClient) { }

ngOnInit(): void {
const headers = new HttpHeaders({
Authorization: `Bearer ${localStorage.getItem('token')}`
});
console.log(localStorage.getItem('token'));
this.http.get('http://localhost:8000/user', {headers}).subscribe(
result => this.user = result
);
}
}

In its html template:

// app/secure/secure.component.html
<h3 *ngIf="user"> Hello, {{user.first_name}} {{user.last_name}}</h3>

Now, in the browser, when you log in it should display user’s name;

In this section we want de write logic for some UI components depending on the user authentication status. If user is logged in we want to display the button ‘log out’ and if he is not we want to let him log in, with a corresponding button.

We have updated routes variable and added loggedout rout.

Create a new component:

ng g c loggedout

Refresh routes variable:

// src/app/app.module.ts
const routes = [
{path: 'login', component: LoginComponent},
{path: 'loggedout', component: LoggedoutComponent},
{path: 'secure', component: SecureComponent}
];

Create a service:

ng g service User

Fill it with the code. You may see login and logout function in the User Service. For logout we just remove the token from the localStorage variable.

// src/app/user.service.ts
import { Injectable } from '@angular/core';
import {Subject} from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class UserService {
private loggedChanged = new Subject<boolean>();
constructor() { }

login(token: any): void {
localStorage.setItem('token', token);
this.loggedChanged.next(true);
}

logout(): void {
localStorage.removeItem('token');
this.loggedChanged.next(false);
}
isUserLoggedIn(): Subject<boolean> {
return this.loggedChanged;
}
}

Make changes in Login component, adding UserService into the constructor, and passing some values in the submit function. We have replaced also login function with the one from User Service and we are passing access token as a parameter.

// src/app/login/login.component.ts
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup} from '@angular/forms';
import {HttpClient} from '@angular/common/http';
import {Router} from '@angular/router';
import {UserService} from '../user.service';

@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
form: FormGroup;
constructor(
private fb: FormBuilder,
private http: HttpClient,
private router: Router,
private userService: UserService
) { }

ngOnInit(): void {
this.form = this.fb.group({
email: '',
password: ''
});
}

onSubmit(): void {
const formData = this.form.getRawValue();
const data = {
username: formData.email,
password: formData.password,
grant_type: 'password',
client_id: 2,
client_secret: 'LYmFJusiC5doqlM5dHGN3Efb0eSGmcPWJZw0Fkqk',
scope: '*'
};

this.http.post('http://localhost:8000/oauth/token', data).subscribe(
(result: any) => {
// localStorage.setItem('token', result.access_token);
this.userService.login(result.access_token);
this.router.navigate(['/secure']);
},
error => {
console.log('error');
console.log(error);
}
);
}
}

Call UserService in app.component.ts:

Notice that we have written logout function, implemented in User Service.

// src/app/app.component.ts
import {Component, OnInit} from '@angular/core';
import {UserService} from './user.service';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'authFrontend2';
loggedIn;
constructor(private userService: UserService) {
}
ngOnInit(): void {
this.userService.isUserLoggedIn().subscribe(
status => this.loggedIn = status
);
console.log('isLogged', this.loggedIn);
}

logout(): void {
this.userService.logout();
}
}

Adding some navigation to app.component.html. Here we may see, we are displaying log in or log out button depending on the loggedIn boolean variable:

// src/app/app.component.html
<nav>
<a routerLink="/login" *ngIf="!loggedIn">Log in</a>
<a routerLink="/loggedout" *ngIf="loggedIn" (click)="logout()">Log out</a>
</nav>

<router-outlet></router-outlet>

As well we can improve the code by adding redirection to the seccure page. Every time when we get an error we will be redirected to the /login route. We have included a Router and User Service and called it in http reques — error case.

// src/app/secure/secure.component.ts
import { Component, OnInit } from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {UserService} from '../user.service';
import {Router} from '@angular/router';

@Component({
selector: 'app-secure',
templateUrl: './secure.component.html',
styleUrls: ['./secure.component.css']
})
export class SecureComponent implements OnInit {
user;

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

ngOnInit(): void {
const headers = new HttpHeaders({
Authorization: `Bearer ${localStorage.getItem('token')}`
});
console.log(localStorage.getItem('token'));
this.http.get('http://localhost:8000/user', {headers}).subscribe(
result => this.user = result,
error => {
this.userService.logout();
this.router.navigate(['/login']);
}
);
}
}

First we stared with Angular. Create new componentcalled Register:

ng g c register

Update your routes variable:

// src/app/app.module.ts
const routes = [
{path: 'login', component: LoginComponent},
{path: 'loggedout', component: LoggedoutComponent},
{path: 'secure', component: SecureComponent},
{path: 'register', component: RegisterComponent}
];

In register.component.ts we will use group builder, provided by angular, it is very handy because of validators it brings:

// src/app/register/register.component.ts
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {HttpHeaders} from '@angular/common/http';

@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit {
form: FormGroup;
constructor(private fb: FormBuilder) { }

ngOnInit(): void {
this.form = this.fb.group({
first_name: ['', Validators.required],
last_name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
password: ['', Validators.required],
password_confirmation: ['', Validators.required]
});
}
}

Its HTML template:

// src/app/register/register.component.html<h2>Please, Register</h2>
<form [formGroup]="form" (submit)="onSubmit()">
<input type="text" formControlName="first_name" placeholder="first name"> <br>
<input type="text" formControlName="last_name" placeholder="last name"> <br>
<input type="email" formControlName="email" placeholder="email"> <br>
<input type="password" formControlName="password" placeholder="password"> <br>
<input type="password" formControlName="password_confirmation" placeholder="password confirmation"> <br>
<button [disabled]="!form.valid">Register</button>
</form>

We move to our backend. Open laravel project. Open the api.php file and add declare a new route:

// routes/api.phpRoute::post('/register', '\App\Http\Controllers\UserController@register');

Insert into user controller a new register function.

Important reminder, Hash class is imported from:

use Illuminate\Support\Facades\Hash;

Register function in User Controller.

// app/Http/Controllers/UserController.phppublic function register(Request $request)
{
User::create([
'first_name' => $request->first_name,
'last_name' => $request->first_name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);
return response()->json([
'message' => 'success'
]);
}

Create a request file where you will be validating the data. Type and execute in the terminal:

php artisan make:request UserRegisterRequest

Navigate to newly created file and write validation logic in rules function:

// app/Http/Requests/UserRegisterRequest.phppublic function rules()
{
return [
'first_name' => 'required',
'last_name' => 'required',
'email' => 'required|email',
'password' => 'required|confirmed|min:6'
];
}

In User Controller, register function replace Request parameter by UserRegisterRequest:

// app/Http/Controllers/UserController.phppublic function register(UserRegisterRequest $request){...}

Open again Angular project and now we are going to try to send the request.

// src/app/register/register.component.tsconstructor(private fb: FormBuilder, private http: HttpClient) { }...
onSubmit(): void {
const formData = this.form.getRawValue();
this.http.post('http://localhost:8000/register', formData).subscribe(
response => console.log(response),
error => console.log(error)
);
}

Go to the browser and try to submit data. The application should be working great.

A guy who is studying full stack web development