{"id":24732582,"url":"https://github.com/gonzalo123/totp","last_synced_at":"2026-04-13T16:04:25.170Z","repository":{"id":66582177,"uuid":"182863797","full_name":"gonzalo123/totp","owner":"gonzalo123","description":"Playing with TOTP","archived":false,"fork":false,"pushed_at":"2019-04-22T20:35:28.000Z","size":1411,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-22T16:16:11.123Z","etag":null,"topics":["ionic","mobile","python","totp"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gonzalo123.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-04-22T20:35:00.000Z","updated_at":"2021-05-09T17:45:03.000Z","dependencies_parsed_at":"2023-06-02T01:16:01.728Z","dependency_job_id":null,"html_url":"https://github.com/gonzalo123/totp","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gonzalo123/totp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gonzalo123%2Ftotp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gonzalo123%2Ftotp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gonzalo123%2Ftotp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gonzalo123%2Ftotp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gonzalo123","download_url":"https://codeload.github.com/gonzalo123/totp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gonzalo123%2Ftotp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31759576,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-13T15:25:13.801Z","status":"ssl_error","status_checked_at":"2026-04-13T15:25:09.162Z","response_time":93,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ionic","mobile","python","totp"],"created_at":"2025-01-27T17:53:02.998Z","updated_at":"2026-04-13T16:04:25.137Z","avatar_url":"https://github.com/gonzalo123.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Playing with TOTP (2FA) and mobile applications with ionic\n\nToday I wanna play with Two Factor Authentication. When we speak about 2FA TOTP come to our mind. There're a lot of TOTP client, for example Google Authenticator.\n\nMy 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 an standard TOTP client. Let's start\n\nThe 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.\n\n```python\nfrom flask import Flask, jsonify, abort, render_template, request\nimport os\nfrom dotenv import load_dotenv\nfrom functools import wraps\nimport pyotp\nfrom flask_qrcode import QRcode\n\ncurrent_dir = os.path.dirname(os.path.abspath(__file__))\nload_dotenv(dotenv_path=\"{}/.env\".format(current_dir))\n\ntotp = pyotp.TOTP(os.getenv('TOTP_BASE32_SECRET'))\n\napp = Flask(__name__)\nQRcode(app)\n\n\ndef verify(key):\n    return totp.verify(key)\n\n\ndef authorize(f):\n    @wraps(f)\n    def decorated_function(*args, **kws):\n        if not 'Authorization' in request.headers:\n            abort(401)\n\n        data = request.headers['Authorization']\n        token = str.replace(str(data), 'Bearer ', '')\n\n        if token != os.getenv('BEARER'):\n            abort(401)\n\n        return f(*args, **kws)\n\n    return decorated_function\n\n\n@app.route('/')\ndef index():\n    return render_template('index.html', totp=pyotp.totp.TOTP(os.getenv('TOTP_BASE32_SECRET')).provisioning_uri(\"gonzalo123.com\", issuer_name=\"TOTP Example\"))\n\n\n@app.route('/check/\u003ckey\u003e', methods=['GET'])\n@authorize\ndef alert(key):\n    status = verify(key)\n    return jsonify({'status': status})\n\n\nif __name__ == \"__main__\":\n    app.run(host='0.0.0.0')\n```\n\nI'll use an standard TOTP client to generate the tokens but with pyotp we can easily create a client also\n\n```python\nimport pyotp\nimport time\nimport os\nfrom dotenv import load_dotenv\nimport logging\n\nlogging.basicConfig(level=logging.INFO)\n\ncurrent_dir = os.path.dirname(os.path.abspath(__file__))\nload_dotenv(dotenv_path=\"{}/.env\".format(current_dir))\n\ntotp = pyotp.TOTP(os.getenv('TOTP_BASE32_SECRET'))\n\nmem = None\nwhile True:\n    now = totp.now()\n    if mem != now:\n        logging.info(now)\n        mem = now\n        time.sleep(1)\n```\n\nAnd finally the mobile application. It's a simple ionic application. That's the view:\n\n```html\n\u003cion-header\u003e\n  \u003cion-toolbar\u003e\n    \u003cion-title\u003e\n      TOTP Validation demo\n    \u003c/ion-title\u003e\n  \u003c/ion-toolbar\u003e\n\u003c/ion-header\u003e\n\n\u003cion-content\u003e\n  \u003cdiv class=\"ion-padding\"\u003e\n    \u003cion-item\u003e\n      \u003cion-label position=\"stacked\"\u003etotp\u003c/ion-label\u003e\n      \u003cion-input placeholder=\"Enter value\" [(ngModel)]=\"totp\"\u003e\u003c/ion-input\u003e\n    \u003c/ion-item\u003e\n    \u003cion-button fill=\"solid\" color=\"secondary\" (click)=\"validate()\" [disabled]=\"!totp\"\u003e\n      Validate\n      \u003cion-icon slot=\"end\" name=\"help-circle-outline\"\u003e\u003c/ion-icon\u003e\n    \u003c/ion-button\u003e\n  \u003c/div\u003e\n\u003c/ion-content\u003e\n```\nThe controller:\n\n````typescript\nimport { Component } from '@angular/core'\nimport { ApiService } from '../sercices/api.service'\nimport { ToastController } from '@ionic/angular'\n\n@Component({\n  selector: 'app-home',\n  templateUrl: 'home.page.html',\n  styleUrls: ['home.page.scss']\n})\nexport class HomePage {\n  public totp\n\n  constructor (private api: ApiService, public toastController: ToastController) {}\n\n  validate () {\n    this.api.get('/check/' + this.totp).then(data =\u003e this.alert(data.status))\n  }\n\n  async alert (status) {\n    const toast = await this.toastController.create({\n      message: status ? 'OK' : 'Not valid code',\n      duration: 2000,\n      color: status ? 'primary' : 'danger',\n    })\n    toast.present()\n  }\n}\n````\n\nI'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\n\n```typescript\nimport { Injectable } from '@angular/core'\nimport { isDevMode } from '@angular/core'\nimport { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'\nimport { CONF } from './conf'\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class ApiService {\n\n  private isDev: boolean = isDevMode()\n  private apiUrl: string\n\n  constructor (private http: HttpClient) {\n    this.apiUrl = this.isDev ? CONF.API_DEV : CONF.API_PROD\n  }\n\n  public get (uri: string, params?: Object): Promise\u003cany\u003e {\n    return new Promise((resolve, reject) =\u003e {\n      this.http.get(this.apiUrl + uri, {\n        headers: ApiService.getHeaders(),\n        params: ApiService.getParams(params)\n      }).subscribe(\n        res =\u003e {this.handleHttpNext(res), resolve(res)},\n        err =\u003e {this.handleHttpError(err), reject(err)},\n        () =\u003e this.handleHttpComplete()\n      )\n    })\n  }\n\n  private static getHeaders (): HttpHeaders {\n\n    const headers = {\n      'Content-Type': 'application/json'\n    }\n\n    headers['Authorization'] = 'Bearer ' + CONF.bearer\n\n    return new HttpHeaders(headers)\n  }\n\n  private static getParams (params?: Object): HttpParams {\n    let Params = new HttpParams()\n    for (const key in params) {\n      if (params.hasOwnProperty(key)) {\n        Params = Params.set(key, params[key])\n      }\n    }\n\n    return Params\n  }\n\n  private handleHttpError (err) {\n    console.log('HTTP Error', err)\n  }\n\n  private handleHttpNext (res) {\n    console.log('HTTP response', res)\n  }\n\n  private handleHttpComplete () {\n    console.log('HTTP request completed.')\n  }\n}\n```\n\nAnd that's all. Here one video with a working example of the prototype:\n\n[![Playing with TOTP](https://img.youtube.com/vi/bgljQu0RVNs/0.jpg)](https://www.youtube.com/watch?v=bgljQu0RVNs)\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgonzalo123%2Ftotp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgonzalo123%2Ftotp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgonzalo123%2Ftotp/lists"}