ang

Creating Login System in Angular and FastAPI

By Rasyue | On December 4, 2020

In this tutorial we are going to build a simple login using Angular for the frontend application and FastAPI for the backend application.

Angular & FastAPI

To start things off, let’s first create our Angular app for the frontend. After we have finish creating the necessary thing in our frontend app, then only will we move to our backend.

Run below command. Make sure you have Node js installed.

npm install -g @angular/cli

ng new my-app

cd my-app

npm start

With that, you will have you frontend application now running. But its the default application. We will have to create our own login component and services.

angular fastapi

Open your project folder in the editor of your choice. I will be using Visual Studio.

angular fastapi folder

To make things simple, we would not be creating the layout component and other stuff, instead we only have to create two components, one for Login and another one for Profile, for when we have logged in.

In your command prompt, make sure you cd into your app folder.

cd src/app

ng g c main/login

ng g c main/profile

You now have 2 newly created components in app/main folder with both in their respective folder.

Open you app-routing.module.ts and add the following.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './main/login/login.component';
import { ProfileComponent } from './main/profile/profile.component';

const routes: Routes = [
  {
    path: 'login',
    component: LoginComponent
  },
  {
    path: 'profile',
    component: ProfileComponent
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

You should have something like this.

Go to your browser and try to go each route, /login and /profile.

At this point, both routes are accessible, but what we want is to have the profile component to only be accessible if the user is logged in.

Angular AuthGuard

To modify a page/route/component accessibility we can use AuthGuard.

First, let’s create a service for our AuthGuard class. Run the following.

ng g service services/auth-guard

And with that, you should have something like this.

Now, open up your AuthGuard and paste the following.

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, Route } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';
@Injectable({
  providedIn: 'root'
})
export class AuthGuardService {

  constructor(
    private _authService: AuthService,
    private _router: Router
  ) { }

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    if (this._authService.getToken()) {
      return true;
    }

    // navigate to login page
    this._router.navigate(['/login']);
    // you can save redirect url so after authing we can move them back to the page they requested
    return false;
  }
}

You probably have already noticed that we do not have the AuthService class yet. Let’s go ahead and create that first.

ng g service services/auth

Open up the auth-service.ts and paste the following.

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

@Injectable({
    providedIn: 'root'
})
export class AuthService {
    constructor() {
    }

    getUserDetails() {
        return localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : null;
    }
    
    setDataInLocalStorage(variableName, data) {
        localStorage.setItem(variableName, data);
    }

    getToken() {
        return localStorage.getItem('token');
    }

    clearStorage() {
        localStorage.clear();
    }
}

Now, go back to your app-routing.module.ts and paste the following.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './main/login/login.component';
import { ProfileComponent } from './main/profile/profile.component';
import { AuthGuardService } from './services/auth-guard.service';

const routes: Routes = [
   
  { path: '', redirectTo: 'login', pathMatch: 'full' }, 
  {
    path: 'login',
    component: LoginComponent
  },
  {
    path: 'profile',
    component: ProfileComponent,
    canActivate:[AuthGuardService] 
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Go to your browser and test out both routes, login and profile.

You will notice that upon trying to go to profile, you will get redirect to login page. If you still can go to profile, make sure there is no existing token in your browser local storage which you have set before.

Explanation

What we did in the above was creating two separate services, AuthGuard and Auth. The AuthGuard service is used to guard the route. How it works is that whenever a User tries to go to a route, the AuthGuard checks whether if the User has a token set in his/her browser.

If you open the Auth service, you can see we have 4 different methods that deal with the localStorage.

You might be asking when does the token get set then? Well, the token will get set after a successful login.

During a login, we will send the User’s username and password to the backend to check with the database, if it exists, we will send back a token to the frontend and the token will be set into the localStorage.

Not only that, after successful login, we can include the token in the request header every time we make an API call to a guarded endpoint.

Sounds cool? Lets code it.

Login Page

Let’s clean up our site looks a bit. Go to app.component.html and delete everything. Replace it with below.

<router-outlet></router-outlet>

I will not be styling the page though, I will leave that to you. Go crazy with your page’s look and style.

You should have something like this.

angular fastapi login

Open your login.component.html and paste the following.

<div>
    <form [formGroup] = 'form' (ngSubmit) = 'login()'>
        <label>Username</label>
        <input type = 'text' formControlName = 'username' />
        <label>Password</label>
        <input type = 'password' formControlName = 'password' />
        <div>
            <button type ='submit' [disabled] = '!this.form.valid'>
                Login
            </button>
        </div>
    </form>
</div>

Open your login.component.ts and paste the following.

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
  form: FormGroup
  constructor(
    public fb: FormBuilder
  ) { }

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

  login(){
    let b = this.form.value
    console.log(b)
  }

}

Make sure you import the ReactiveFormsModule and FormsModule in your app.module.ts or else you will get errors.

Test out your login now and upon submit you should a log in your console.

Now that the login box is working, the next part is making the API call to our backend.

Angular FastAPI : API Service

We will need to create 2 new services, one to make API calls and another one as an interceptor to modify our request header.

Run the following command.

ng g service services/api
ng g service services/interceptor-service

Open your api.service and paste the following.

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class ApiService {


  private REST_API_SERVER = "http://localhost:8000/";
  constructor(private httpClient: HttpClient) { }

  getTypeRequest(url) {
    return this.httpClient.get(this.REST_API_SERVER+url).pipe(map(res => {
      return res;
    }));
  }

  postTypeRequest(url, payload) {
    return this.httpClient.post(this.REST_API_SERVER+url, payload).pipe(map(res => {
      return res;
    }));
  }

  putTypeRequest(url, payload) {
    return this.httpClient.put(this.REST_API_SERVER+url, payload).pipe(map(res => {
      return res;
    }))
  }  
}

Notice that the REST_API_SERVER value is localhost:8000/. This value corresponds to our backend api.

The above code basically shows that we have 3 functions for each type of request we are going to make, the GET, POST and PUT.

Now open your interceptor-service and paste the following.

import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpRequest, HttpResponse, HttpErrorResponse } from '@angular/common/http';
import { Observable, Subject, of, throwError } from 'rxjs';
import { map } from 'rxjs/operators';
import { Router } from '@angular/router';

import { tap, catchError } from 'rxjs/operators';
import { AuthService } from './auth.service';

@Injectable({
  providedIn: 'root'
})
export class InterceptorService {

  constructor(
    private router: Router,
    private _auth: AuthService
  ) {
  }

  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    if (!request.headers.has('Content-Type')) {
      request = request.clone({ headers: request.headers.set('Content-Type', 'application/json') });
    }
    request = request.clone({ headers: request.headers.set('Accept', 'application/json') }).clone({
      setHeaders: {
        Authorization: `Bearer ${this._auth.getToken()}`
      }
    });    

    return next.handle(request)
  }
}

Open your app.module.ts and paste the following.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LoginComponent } from './main/login/login.component';
import { ProfileComponent } from './main/profile/profile.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { InterceptorService } from './services/interceptor-service.service'

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    ProfileComponent,
    
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ReactiveFormsModule,
    FormsModule,
    HttpClientModule
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: InterceptorService,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Make sure your components and the interceptor service are imported as well as the ReactiveFormsModule, FormsModule

Open your login.component.html and paste the following.

<div>
    <form [formGroup] = 'form' (ngSubmit) = 'login()'>
        <label>Username</label>
        <input type = 'text' formControlName = 'username' />
        <label>Password</label>
        <input type = 'password' formControlName = 'password' />
        <div>
            <button type ='submit' [disabled] = '!this.form.valid'>
                Login
            </button>
        </div>
    </form>
</div>

Now open your login.component.ts and paste the following.

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from 'src/app/services/auth.service';
import {ApiService} from '../../services/api.service'

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
  form: FormGroup
  constructor(
    private _api : ApiService,
    private _auth: AuthService,
    private router: Router,
    public fb: FormBuilder
  ) { }

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

  login(){
    let b = this.form.value
    console.log(b)
    this._api.postTypeRequest('login', b).subscribe((res: any) => {
      console.log(res)
      if(res.access_token){
        this._auth.setDataInLocalStorage('token', res.access_token)
        this.router.navigate(['profile'])
      }
    }, err => {
      console.log(err)
    });
  }

}

You should have something like this.

angular fastapi login 2

For your profile.component.html, paste the following.

<p>Login Successful!</p>

For your profile.component.ts, paste the following.

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from 'src/app/services/auth.service';
import {ApiService} from '../../services/api.service'

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

  constructor(
    private _api : ApiService,
    private _auth: AuthService,
  ) { }

  ngOnInit(): void {
    this.test_jwt()
  }

  test_jwt(){
    this._api.getTypeRequest('test-jwt').subscribe((res: any) => {
      console.log(res)

    }, err => {
      console.log(err)
    });
  }
}

Upon successful login, you will be redirected to the profile page and the test_jwt() will automatically run to show you that the JWT is working and you can access the JWT protected endpoint.

It’s time to build our backend.

Angular FastAPI Backend

Let’s create our backend now. Make sure you have Python installed, I am using Anaconda but it works the same.

mkdir backend

cd backend

python -m venv C:\backend\venv

venv\Scripts\activate

pip install fastapi

pip install uvicorn

This will create a virtual env for your backend and also install FastAPI and uvicorrn.

Create a new file name main.py. Open it and paste the following.

from typing import Optional

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World, Rasyue"}

To run your backend application, run the following.

uvicorn main:app --reload

Go to localhost:8000 to test it out.

FastAPI Login Endpoint and JWT

First thing first, install the jwt.

pip install fastapi-jwt-auth

Open your main.py and paste the following.

from typing import Optional
from fastapi import FastAPI, HTTPException, Depends, Request
from fastapi.responses import JSONResponse
from fastapi_jwt_auth import AuthJWT
from fastapi_jwt_auth.exceptions import AuthJWTException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

app = FastAPI()

origins = [
    "http://localhost",
    "http://localhost:4200",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
class User(BaseModel):
    username: str
    password: str

class Settings(BaseModel):
    authjwt_secret_key: str = "my_jwt_secret"

@AuthJWT.load_config
def get_config():
    return Settings()


@app.exception_handler(AuthJWTException)
def authjwt_exception_handler(request: Request, exc: AuthJWTException):
    return JSONResponse(
        status_code=exc.status_code,
        content={"detail": exc.message}
    )

@app.get("/")
def read_root():
    return {"Hello": "World Rasyue"}


@app.post('/login')
def login(user: User, Authorize: AuthJWT = Depends()):
    #user.username
    #user.password
    # this is the part where we will check the user credentials with our database record
    #but since we are not going to use any db, straight away we will just create the token and send it back
    # subject identifier for who this token is for example id or username from database
    access_token = Authorize.create_access_token(subject=user.username)
    return {"access_token": access_token}


@app.get('/test-jwt')
def user(Authorize: AuthJWT = Depends()):
    
    Authorize.jwt_required()
    return {"user": 123124124, 'data': 'jwt test works'} 
    #current_user = Authorize.get_jwt_subject()
    #return {"user": current_user, 'data': 'jwt test works'}    

And that is it for our backend. Remember to run with the command uvicorn main:app --reload

Now go back to your frontend and test the login system.

The End..

With that, we have successfully create a login system for our app using Angular for the frontend and FastAPI for the backend.

Stay tuned for more.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*
*