Today I want to play with Two Factor Authentication. When we speak about 2FA, TOTP come to our mind. There’re a lot of TOTP clients, for example Google Authenticator.
My idea with this prototype is to build one Mobile application (with ionic) and validate one totp token in a server (in this case a Python/Flask application). The token will be generated with a standard TOTP client. Let’s start
The sever will be a simple Flask server to handle routes. One route (GET /) will generate one QR code to allow us to configure or TOTP client. I’m using the library pyotp to handle totp operations.
from flask import Flask, jsonify, abort, render_template, request import os from dotenv import load_dotenv from functools import wraps import pyotp from flask_qrcode import QRcode current_dir = os.path.dirname(os.path.abspath(__file__)) load_dotenv(dotenv_path="{}/.env".format(current_dir)) totp = pyotp.TOTP(os.getenv('TOTP_BASE32_SECRET')) app = Flask(__name__) QRcode(app) def verify(key): return totp.verify(key) def authorize(f): @wraps(f) def decorated_function(*args, **kws): if not 'Authorization' in request.headers: abort(401) data = request.headers['Authorization'] token = str.replace(str(data), 'Bearer ', '') if token != os.getenv('BEARER'): abort(401) return f(*args, **kws) return decorated_function @app.route('/') def index(): return render_template('index.html', totp=pyotp.totp.TOTP(os.getenv('TOTP_BASE32_SECRET')).provisioning_uri("gonzalo123.com", issuer_name="TOTP Example")) @app.route('/check/<key>', methods=['GET']) @authorize def alert(key): status = verify(key) return jsonify({'status': status}) if __name__ == "__main__": app.run(host='0.0.0.0')
I’ll use an standard TOTP client to generate the tokens but with pyotp we can easily create a client also
import pyotp import time import os from dotenv import load_dotenv import logging logging.basicConfig(level=logging.INFO) current_dir = os.path.dirname(os.path.abspath(__file__)) load_dotenv(dotenv_path="{}/.env".format(current_dir)) totp = pyotp.TOTP(os.getenv('TOTP_BASE32_SECRET')) mem = None while True: now = totp.now() if mem != now: logging.info(now) mem = now time.sleep(1)
And finally the mobile application. It’s a simple ionic application. That’s the view:
<ion-header> <ion-toolbar> <ion-title> TOTP Validation demo </ion-title> </ion-toolbar> </ion-header> <ion-content> <div class="ion-padding"> <ion-item> <ion-label position="stacked">totp</ion-label> <ion-input placeholder="Enter value" [(ngModel)]="totp"></ion-input> </ion-item> <ion-button fill="solid" color="secondary" (click)="validate()" [disabled]="!totp"> Validate <ion-icon slot="end" name="help-circle-outline"></ion-icon> </ion-button> </div> </ion-content>
The controller:
import { Component } from '@angular/core' import { ApiService } from '../sercices/api.service' import { ToastController } from '@ionic/angular' @Component({ selector: 'app-home', templateUrl: 'home.page.html', styleUrls: ['home.page.scss'] }) export class HomePage { public totp constructor (private api: ApiService, public toastController: ToastController) {} validate () { this.api.get('/check/' + this.totp).then(data => this.alert(data.status)) } async alert (status) { const toast = await this.toastController.create({ message: status ? 'OK' : 'Not valid code', duration: 2000, color: status ? 'primary' : 'danger', }) toast.present() } }
I’ve also put a simple security system. In a real life application we’ll need something better, but here I’ve got a Auth Bearer harcoded and I send it en every http request. To do it I’ve created a simple api service
import { Injectable } from '@angular/core' import { isDevMode } from '@angular/core' import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' import { CONF } from './conf' @Injectable({ providedIn: 'root' }) export class ApiService { private isDev: boolean = isDevMode() private apiUrl: string constructor (private http: HttpClient) { this.apiUrl = this.isDev ? CONF.API_DEV : CONF.API_PROD } public get (uri: string, params?: Object): Promise<any> { return new Promise((resolve, reject) => { this.http.get(this.apiUrl + uri, { headers: ApiService.getHeaders(), params: ApiService.getParams(params) }).subscribe( res => {this.handleHttpNext(res), resolve(res)}, err => {this.handleHttpError(err), reject(err)}, () => this.handleHttpComplete() ) }) } private static getHeaders (): HttpHeaders { const headers = { 'Content-Type': 'application/json' } headers['Authorization'] = 'Bearer ' + CONF.bearer return new HttpHeaders(headers) } private static getParams (params?: Object): HttpParams { let Params = new HttpParams() for (const key in params) { if (params.hasOwnProperty(key)) { Params = Params.set(key, params[key]) } } return Params } private handleHttpError (err) { console.log('HTTP Error', err) } private handleHttpNext (res) { console.log('HTTP response', res) } private handleHttpComplete () { console.log('HTTP request completed.') } }
And that’s all. Here one video with a working example of the prototype:
Source code here