2 layer authentication in app (username, password, token)

@Tobs,

Yes, a single page application could work, but there are a couple of issues to think about when implementing one.

Bookmarks - You cant save a specific location easily, especially user friendly when you have lots of different layouts.

Difficulty - You’d have to work with the index match essentially and cannot use Dash’s cool pages


@beginof,

When implementing any sort of 2fa, you always have to keep in mind not allowing people to access information when halfway through the process, this also includes things mentioned in this topic:

Go towards the end with discussion about payloads getting responses, etc. making sure that user’s cannot access info even if they know the structure if they are not logged in.

Steps:

  • Allow user to log in
  • Only logged in users are able to verify
  • Once both steps are done give access to the whole site

Requirements, you cannot skip any steps.


The best way I think to work with this is to use a before_request handler from the underlying flask server, something like this:

@server.before_request
def pull_identity():
    request = flask.request
    if '/login' in request.url or '/logout' in request.url or '/static' in request.url:
        return
    else:
        try:
            if flask_login.current_user.id:
                if '/verify' in request.url or '/mfa' in request.url:
                    return
                if session['verified']:
                    return
        except:
            pass
        return flask.redirect('/login')

This punches holes for specific requests to get through unless at certain steps in the process.

As said in the other topic I mentioned, I find it better to handle the login and verification process in the flask routing itself and then just pass the verified user to the dash application.

Now, for the other steps in the process its up to how you want to do it, obviously, you’ll need a db to store the association with mfa and the user. This is necessary for a TOTP flow, with is what you are after.

For creating, I use the library pyotp:

This is the response to generate the qr code for signing up:

if info['type'] == 'authapp':
    session['temp'] = pyotp.random_base32()
    return jsonify({'data': pyotp.totp.TOTP(session['temp']).provisioning_uri(name=flask_login.current_user.id,
                                                                      issuer_name=yourwebsite)})

This is how I get the data to show in a qrcode fashion from the response generated from above:

async function Add() {
        dict = {}
        dict['command'] = 'add'
        dict['type'] = $("#type").val()
        dict['provider'] = $("#provider").val()
        dict['detail'] = $("#detail").val()
        if (dict != {}) {
            json_data = JSON.stringify(dict)
            const Url='../MFA';
            const othePram = {
            headers:{
                "content-type":"application/json; charset=UTF-8"
                },
                body:json_data,
                method:"POST"
                };

            const Response = await fetch(Url,othePram)
            .then(response=>response.json())
            .then(data=>{ return data; })

            if (!Response['data'].includes('failed')) {
                if ($("#type").val() == 'phone') {
                    $("#mfa_alert").text(Response['data'])
                    $("#qrcode_details").hide()
                } else {
                    var qrcode = new QRCode("qrcode")
                    qrcode.makeCode(Response['data'])
                    $("#qrcode_details").show()
                }
                $("#add").hide()
                $("#verify").show()
            }
        }
    }

For verifying the user, you’ll have to compare their given code with what you have stored in the db:

if pyotp.TOTP(decrypt(stored_secret_key)).verify(flask.request.form['code']):
    session['verified'] = 'true'

Something like the above.


With all of this said, you should be able to come up with your own flow, my recommendation is to add the before_request last because it can add some layer of being tricky.

You can build this flow inside of dash, but it becomes a lot more tricky to secure it completely.


Now, a lot of applications are moving away from logging in and verifying users directly and going with other things like Microsoft, Apple and Google authentication, so you might want to instead consider moving in that direction instead of trying to make your own process.

You could use a library like Flask-Dance instead.

2 Likes