User Authentication With Angular 4 and Flask
In this tutorial, we’ll demonstrate how to set up token-based authentication (via JSON Web Tokens) with Angular 4 and Flask.
Main Dependencies:
Auth Workflow
Here’s the full user auth process:
- Client logs in and the credentials are sent to the server
- If the credentials are correct, the server generates a token and sends it as a response to the client
- Client receives and stores the token in Local Storage
- Client then sends token to server on subsequent requests within the request header
Project Setup
Start by globally installing the Angular CLI:
$ npm install -g @angular/[email protected]
Then generate a new Angular 4 project boilerplate:
$ ng new angular4-auth
Fire up the app after the dependencies install:
$ cd angular4-auth $ ng serve
It will probably take a minute or two to compile and build your application. Once done, navigate to
Open up the project in your favorite code editor, and then glance over the code:
├── e2e │ ├── app.e2e-spec.ts │ ├── app.po.ts │ └── tsconfig.e2e.json ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── src │ ├── app │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ └── app.module.ts │ ├── assets │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── typings.d.ts ├── tsconfig.json └── tslint.json
In short, the client-side code lives in the “src” folder and the Angular app itself can be found in the “app” folder.
Take note of the AppModule
within app.module.ts. This is used to bootstrap the Angular app. The @NgModule
decorator takes metadata that lets Angular know how to run the app. Everything that we create in this tutorial will be added to this object.
Make sure you have a decent grasp of the app structure before moving on.
NOTE: Just getting started with Angular 4? Review the Angular Style Guide, since the app generated from the CLI follows the recommended structure from that guide, as well as the Angular4Crud Tutorial.
Did you notice that the CLI initialized a new Git repo? This part is optional, but it’s a good idea to create a new Github repository and update the remote:
$ git remote set-url origin <newurl>
Now, let’s wire up a new component…
Auth Component
First, use the CLI to generate a new Login component:
$ ng generate component components/login
This set up the component files and folders and even wired it up to app.module.ts. Next, let’s change the login.component.ts file to the following:
import { Component } from '@angular/core'; @Component({ selector: 'login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent { test: string = 'just a test'; }
If you haven’t used TypeScript before, then this code probably looks pretty foreign to you. TypeScript is a statically-typed superset of JavaScript that compiles to vanilla JavaScript, and it is the de facto programming language for building Angular 4 apps.
In Angular 4, we define a component by wrapping a config object with an @Component
decorator. We can share code between packages by importing the classes we need; and, in this case, we import Component
from the @angular/core
package. The LoginComponent
class is the component’s controller, and we use the export
operator to make it available for other classes to import.
Add the following HTML to the login.component.html:
<h1>Login</h1> <p>{{test}}</p>
Next, configure the routes, via the RouterModule in the app.module.ts file:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; import { LoginComponent } from './components/login/login.component'; @NgModule({ declarations: [ AppComponent, LoginComponent, ], imports: [ BrowserModule, RouterModule.forRoot([ { path: 'login', component: LoginComponent } ]) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Finish enabling routing by replacing all HTML in the app.component.html file with the <router-outlet>
tag:
<router-outlet></router-outlet>
Run ng serve
in your terminal, if you haven’t already, and then navigate to http://localhost:4200/login. If all went well you should see the just a test
text.
Bootstrap Style
To quickly add some style, update the index.html, adding in Bootstrap and wrapping the <app-root></app-root>
in a container:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Angular4Auth</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> </head> <body> <div class="container"> <app-root></app-root> </div> </body> </html>
You should see the app auto reload as soon as you save.
Auth Service
Next, let’s create a global service to handle a user logging in, logging out, and signing up:
$ ng generate service services/auth
Edit the auth.service.ts so that it has the following code:
import { Injectable } from '@angular/core'; @Injectable() export class AuthService { test(): string { return 'working'; } }
Remember how providers worked in Angular 1? They were global objects that stored a single state. When the data in a provider changed, any object that had injected that provider would receive the updates. In Angular 4, providers retain their special behavior and they are defined with the @Injectable
decorator.
Sanity Check
Before adding anything significant to AuthService
, let’s make sure the service itself is wired up correctly. To do that, within login.component.ts inject the service and call the test()
method:
import { Component, OnInit } from '@angular/core'; import { AuthService } from '../../services/auth.service'; @Component({ selector: 'login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent implements OnInit { test: string = 'just a test'; constructor(private auth: AuthService) {} ngOnInit(): void { console.log(this.auth.test()); } }
We’ve introduced some new concepts and keywords. The constructor()
function is a special method that we use to set up a new instance of a class. The constructor()
is where we pass any parameters that the class requires, including any providers (i.e., AuthService
) that we want to inject. In TypeScript, we can hide variables from the outside world with the private
keyword. Passing a private
variable in the constructor is a shortcut to defining it within the class and then assigning the argument’s value to it. Notice how the auth
variable is accessible to the this
object after it is passed into the constructor.
We implement the OnInit
interface to ensure that we explicitly define a ngOnInit()
function. Implementing OnInit
ensures that our component will be called after the first change detection check. This function is called once when the component first initializes, making it the ideal place to configure data that relies on other Angular classes.
Unlike components, which are automatically added, services have to be manually imported and configured on the @NgModule
. So, to get it working, you’ll also have to import the AuthService
in app.module.ts and add it to the providers
:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; import { LoginComponent } from './components/login/login.component'; import { AuthService } from './services/auth.service'; @NgModule({ declarations: [ AppComponent, LoginComponent, ], imports: [ BrowserModule, RouterModule.forRoot([ { path: 'login', component: LoginComponent } ]) ], providers: [AuthService], bootstrap: [AppComponent] }) export class AppModule { }
Run the server and then navigate to http://localhost:4200/login. You should see working
logged to the JavaScript console.
User Login
To handle logging a user in, update the AuthService
like so:
import { Injectable } from '@angular/core'; import { Headers, Http } from '@angular/http'; import 'rxjs/add/operator/toPromise'; @Injectable() export class AuthService { private BASE_URL: string = 'http://localhost:5000/auth'; private headers: Headers = new Headers({'Content-Type': 'application/json'}); constructor(private http: Http) {} login(user): Promise<any> { let url: string = `${this.BASE_URL}/login`; return this.http.post(url, user, {headers: this.headers}).toPromise(); } }
We employ the help of some built-in Angular classes, Headers
and Http
, to handle our AJAX calls to the server.
Also, update the app.module.ts file to import the HttpModule
.
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { HttpModule } from '@angular/http'; import { AppComponent } from './app.component'; import { LoginComponent } from './components/login/login.component'; import { AuthService } from './services/auth.service'; @NgModule({ declarations: [ AppComponent, LoginComponent, ], imports: [ BrowserModule, HttpModule, RouterModule.forRoot([ { path: 'login', component: LoginComponent } ]) ], providers: [AuthService], bootstrap: [AppComponent] }) export class AppModule { }
Here, we are using the Http service to send an AJAX request to the /user/login
endpoint. This returns a promise object.
NOTE: Make sure to remove
console.log(this.auth.test());
from theLoginComponent
component.
User Registration
Let’s go ahead and add the ability to register a user as well, which is similar to logging a user in. Update src/app/services/auth.service.ts, taking note of the register
method:
import { Injectable } from '@angular/core'; import { Headers, Http } from '@angular/http'; import 'rxjs/add/operator/toPromise'; @Injectable() export class AuthService { private BASE_URL: string = 'http://localhost:5000/auth'; private headers: Headers = new Headers({'Content-Type': 'application/json'}); constructor(private http: Http) {} login(user): Promise<any> { let url: string = `${this.BASE_URL}/login`; return this.http.post(url, user, {headers: this.headers}).toPromise(); } register(user): Promise<any> { let url: string = `${this.BASE_URL}/register`; return this.http.post(url, user, {headers: this.headers}).toPromise(); } }
Now, to test this we need to set up a back end…
Server-side Setup
For the server-side, we’ll use the finished project from a previous blog post, Token-Based Authentication With Flask. You can view the code from the flask-jwt-auth repository.
NOTE: Feel free to use your own server, just make sure to update the
baseURL
in theAuthService
.
Clone the project structure in a new terminal window:
$ git clone https://github.com/realpython/flask-jwt-auth
Follow the directions in the README to set up the project, making sure the tests pass before moving on. Once done, run the server with python manage.py runserver
, which will listen on port 5000.
Sanity Check
To test, update LoginComponent
to use the login
and register
methods from the service:
import { Component, OnInit } from '@angular/core'; import { AuthService } from '../../services/auth.service'; @Component({ selector: 'login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent implements OnInit { test: string = 'just a test'; constructor(private auth: AuthService) {} ngOnInit(): void { let sampleUser: any = { email: '[email protected]' as string, password: 'michael' as string }; this.auth.register(sampleUser) .then((user) => { console.log(user.json()); }) .catch((err) => { console.log(err); }); this.auth.login(sampleUser).then((user) => { console.log(user.json()); }) .catch((err) => { console.log(err); }); } }
Refresh http://localhost:4200/login in the browser and you should see a success in the JavaScript console, after the user is logged in, with the token:
{ "auth_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1M…jozfQ.bPNQb3C98yyNe0LDyl1Bfkp0Btn15QyMxZnBoE9RQMI", "message": "Successfully logged in.", "status": "success" }
Auth Login
Update login.component.html:
<div class="row"> <div class="col-md-4"> <h1>Login</h1> <hr><br> <form (ngSubmit)="onLogin()" novalidate> <div class="form-group"> <label for="email">Email</label> <input type="text" class="form-control" id="email" placeholder="enter email" [(ngModel)]="user.email" name="email" required> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" class="form-control" id="password" placeholder="enter password" [(ngModel)]="user.password" name="password" required> </div> <button type="submit" class="btn btn-default">Submit</button> </form> </div> </div>
Take note of the form. We used the [(ngModel)]
directive on each of the form inputs to capture those values in the controller. Also, when the form is submitted, the ngSubmit
directive handles the event by firing the onLogin()
method.
Now, let’s update the component code, adding in onLogin()
:
import { Component } from '@angular/core'; import { AuthService } from '../../services/auth.service'; import { User } from '../../models/user'; @Component({ selector: 'login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent { user: User = new User(); constructor(private auth: AuthService) {} onLogin(): void { this.auth.login(this.user) .then((user) => { console.log(user.json()); }) .catch((err) => { console.log(err); }); } }
If you have the Angular web server running, you should see the error Cannot find module '../../models/user'
in the browser. Before our code will work, we need to create a User
model.
$ ng generate class models/user
Update src/app/models/user.ts:
export class User { constructor(email?: string, password?: string) {} }
Our User
model has two properties, email
and password
. The ?
character is a special operator that indicates that initializing User
with explicit email
and password
values is optional. This is equivalent to the following class in Python:
class User(object): def __init__(self, email=None, password=None): self.email = email self.password = password
Don’t forget to update auth.service.ts to use the new object.
import { Injectable } from '@angular/core'; import { Headers, Http } from '@angular/http'; import { User } from '../models/user'; import 'rxjs/add/operator/toPromise'; @Injectable() export class AuthService { private BASE_URL: string = 'http://localhost:5000/auth'; private headers: Headers = new Headers({'Content-Type': 'application/json'}); constructor(private http: Http) {} login(user: User): Promise<any> { let url: string = `${this.BASE_URL}/login`; return this.http.post(url, user, {headers: this.headers}).toPromise(); } register(user: User): Promise<any> { let url: string = `${this.BASE_URL}/register`; return this.http.post(url, user, {headers: this.headers}).toPromise(); } }
One last thing. We need to import the FormsModule
in the app.module.ts file.
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { HttpModule } from '@angular/http'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { LoginComponent } from './components/login/login.component'; import { AuthService } from './services/auth.service'; @NgModule({ declarations: [ AppComponent, LoginComponent, ], imports: [ BrowserModule, HttpModule, FormsModule, RouterModule.forRoot([ { path: 'login', component: LoginComponent } ]) ], providers: [AuthService], bootstrap: [AppComponent] }) export class AppModule { }
So, when the form is submitted, we capture the email and password and pass them to the login()
method on the service.
Test this out with-
- email:
[email protected]
- password:
michael
Again, you should see a success in the javaScript console with the token.
Auth Register
Just like for the login functionality, we need to add a component for registering a user. Start by generating a new Register component:
$ ng generate component components/register
Update src/app/components/register/register.component.html:
<div class="row"> <div class="col-md-4"> <h1>Register</h1> <hr><br> <form (ngSubmit)="onRegister()" novalidate> <div class="form-group"> <label for="email">Email</label> <input type="text" class="form-control" id="email" placeholder="enter email" [(ngModel)]="user.email" name="email" required> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" class="form-control" id="password" placeholder="enter password" [(ngModel)]="user.password" name="password" required> </div> <button type="submit" class="btn btn-default">Submit</button> </form> </div> </div>
Then, update src/app/components/register/register.component.ts as follows:
import { Component } from '@angular/core'; import { AuthService } from '../../services/auth.service'; import { User } from '../../models/user'; @Component({ selector: 'register', templateUrl: './register.component.html', styleUrls: ['./register.component.css'] }) export class RegisterComponent { user: User = new User(); constructor(private auth: AuthService) {} onRegister(): void { this.auth.register(this.user) .then((user) => { console.log(user.json()); }) .catch((err) => { console.log(err); }); } }
Add a new route handler to the app.module.ts file:
RouterModule.forRoot([ { path: 'login', component: LoginComponent }, { path: 'register', component: RegisterComponent } ])
Test it out by registering a new user!
Local Storage
Next, let’s add the token to Local Storage for persistence by replacing the console.log(user.json());
with localStorage.setItem('token', user.data.token);
in src/app/components/login/login.component.ts: