Beispiel #1
0
def login():
    devices = [doc['device'] for doc in DB.u2f.find()]
    u2f_enabled = True if devices else False
    if request.method == 'POST':
        csrf.protect()
        pwd = request.form.get('pass')
        if pwd and verify_pass(pwd):
            if devices:
                resp = json.loads(request.form.get('resp'))
                print(resp)
                try:
                    u2f.complete_authentication(session['challenge'], resp)
                except ValueError as exc:
                    print('failed', exc)
                    abort(401)
                    return
                finally:
                    session['challenge'] = None

            session['logged_in'] = True
            return redirect(request.args.get('redirect') or '/admin')
        else:
            abort(401)

    payload = None
    if devices:
        payload = u2f.begin_authentication(ID, devices)
        session['challenge'] = payload

    return render_template(
        'login.html',
        u2f_enabled=u2f_enabled,
        me=ME,
        payload=payload,
    )
Beispiel #2
0
def validate_2fa(username, otp_token, u2f_challenge, u2f_response, storage):
    u2f_enabled = False
    otp_enabled = False
    u2f_error = False
    otp_error = False
    report_errors = False

    # Get user
    user_data = storage.get_user(username)

    # Test u2f
    if config.auth.get('allow_u2f', True):
        registered_keys = user_data.get('u2f_devices', [])
        if registered_keys:
            # U2F is enabled for user
            u2f_enabled = True
            report_errors = True
            if u2f_challenge and u2f_response:
                try:
                    complete_authentication(u2f_challenge, u2f_response,
                                            [APP_ID])
                    return
                except Exception:
                    u2f_error = True

    # Test OTP
    if config.auth.get('allow_2fa', True):
        otp_sk = user_data.get('otp_sk', None)
        if otp_sk:
            # OTP is enabled for user
            otp_enabled = True
            report_errors = True
            if otp_token:
                if get_totp_token(otp_sk) != otp_token:
                    otp_error = True
                else:
                    return

    if report_errors:
        if u2f_error:
            # Wrong response to challenge
            raise AuthenticationException("Wrong U2F Security Token")
        elif otp_error:
            # Wrong token provided
            raise AuthenticationException("Wrong OTP token")
        elif u2f_enabled:
            # No challenge/response provided and U2F is enabled
            raise AuthenticationException("Wrong U2F Security Token")
        elif otp_enabled:
            # No OTP Token provided and OTP is enabled
            raise AuthenticationException("Wrong OTP token")

        # This should never hit
        raise AuthenticationException("Unknown 2FA Authentication error")
Beispiel #3
0
    def test_authenticate_single_soft_u2f(self):
        # Register
        device, token = register_token()

        # Authenticate
        request = begin_authentication(APP_ID, [device])
        data = request.data_for_client

        response = token.getAssertion(FACET, data['appId'], data['challenge'],
                                      data['registeredKeys'][0])

        complete_authentication(request.json, response)
Beispiel #4
0
 def verify_token(self, token):
     client_data = json.loads(websafe_decode(token['clientData']).decode())
     try:
         u2f_challenge = U2FChallenge.objects.get(
             challenge=client_data['challenge'], device=self)
     except U2FChallenge.DoesNotExist:
         return False
     try:
         complete_authentication(u2f_challenge.challenge_data, token,
                                 [settings.OTP_U2F_APP_ID])
     except ValueError:
         return False
     u2f_challenge.delete()
     return True
Beispiel #5
0
    def verify(self, user_name, object_dn, key):

        # Do we have read permissions for the requested attribute
        self.__check_acl(user_name, object_dn, "r")

        # Get the object for the given dn
        uuid = self.__dn_to_uuid(object_dn)
        factor_method = self.get_method_from_user(uuid)
        user_settings = self.__settings[uuid] if uuid in self.__settings else {}
        if factor_method == "otp":
            totp = TOTP(user_settings.get('otp_secret'))
            return totp.verify(key)

        elif factor_method == "u2f":

            challenge = user_settings.pop('_u2f_challenge_')
            data = loads(key)
            device, c, t = complete_authentication(challenge, data, [self.facet])
            return {
                'keyHandle': device['keyHandle'],
                'touch': t,
                'counter': c
            }

        elif factor_method is None:
            return True

        return False
Beispiel #6
0
def validate(request, username):
    import datetime, random

    data = simplejson.loads(request.POST["response"])

    res = check_errors(request, data)
    if res != True:
        return res

    challenge = request.session.pop('_u2f_challenge_')
    device, c, t = complete_authentication(challenge, data,
                                           [settings.U2F_APPID])

    key = User_Keys.objects.get(username=username,
                                properties__shas="$.device.publicKey=%s" %
                                device["publicKey"])
    key.last_used = timezone.now()
    key.save()
    mfa = {"verified": True, "method": "U2F", "id": key.id}
    if getattr(settings, "MFA_RECHECK", False):
        mfa["next_check"] = int((datetime.datetime.now() + datetime.timedelta(
            seconds=random.randint(settings.MFA_RECHECK_MIN,
                                   settings.MFA_RECHECK_MAX))).strftime("%s"))
    request.session["mfa"] = mfa
    return True
def validate_2fa(name):
    form = forms.SignTokenForm()
    if form.validate_on_submit():
        errorCode = json.loads(form.response.data)['errorCode']
        if errorCode != 0:
            flash("Token authentication failed", "error")
            return redirect(url_for('select_2fa'))
        device, c, t = complete_authentication(session['u2f_sign'],
                                               form.response.data, app_id)
        if t != 1:
            flash("Token authentication failed", "error")
            return redirect(url_for('select_2fa'))
        # Log in the user
        user = load_user(session['user'])
        flask_login.login_user(user)
        flash("Login complete", "success")
        return redirect(url_for('index'))
    key = models.U2FCredentials.query.filter_by(owner=session['user'],
                                                name=name).first()
    sign = begin_authentication(app_id, [key.device])
    session['u2f_sign'] = sign.json
    challenge = sign['challenge']
    registeredKeys = json.loads(sign['registeredKeys'][0])
    version = registeredKeys['version']
    keyHandle = registeredKeys['keyHandle']

    return render_template('validate_2fa.html',
                           challenge=challenge,
                           version=version,
                           keyHandle=keyHandle,
                           app_id=app_id,
                           form=form,
                           key=key)
Beispiel #8
0
    def verify(self, user_name, object_dn, key):

        # Do we have read permissions for the requested attribute
        self.__check_acl(user_name, object_dn, "r")

        # Get the object for the given dn
        uuid = self.__dn_to_uuid(object_dn)
        factor_method = self.get_method_from_user(uuid)
        user_settings = self.__settings[
            uuid] if uuid in self.__settings else {}
        if factor_method == "otp":
            totp = TOTP(user_settings.get('otp_secret'))
            return totp.verify(key)

        elif factor_method == "u2f":

            challenge = user_settings.pop('_u2f_challenge_')
            data = loads(key)
            device, c, t = complete_authentication(challenge, data,
                                                   [self.facet])
            return {'keyHandle': device['keyHandle'], 'touch': t, 'counter': c}

        elif factor_method is None:
            return True

        return False
Beispiel #9
0
def validate(request, username):
    data = json.loads(request.POST["response"])

    res = check_errors(request, data)
    if res is not True:
        return res

    challenge = request.session.pop('_u2f_challenge_')
    device, c, t = complete_authentication(challenge, data, [settings.U2F_APPID])

    key = UserKey.objects.get(
        username=username,
        properties__device__publicKey=device["publicKey"]
    )

    key.last_used = timezone.now()
    key.save()

    mfa = {
        "verified": True,
        "method": "U2F",
        "id": key.id
    }

    if getattr(settings, "MFA_RECHECK", False):
        mfa["next_check"] = next_check()

    request.session["mfa"] = mfa
    return True
Beispiel #10
0
    def test_authenticate_single_soft_u2f(self):
        # Register
        device, token = register_token()

        # Authenticate
        request = begin_authentication(APP_ID, [device])
        data = request.data_for_client

        response = token.getAssertion(
            FACET,
            data['appId'],
            data['challenge'],
            data['registeredKeys'][0]
        )

        complete_authentication(request.json, response)
Beispiel #11
0
def _sign_response(user_id, response_data):
    client = get_client()
    user = get_user(user_id)
    sign_response = response_data.signResponse
    challenge = sign_response.clientData.challenge
    request_data = store.retrieve(client.id, user_id, challenge)
    if request_data is None:
        raise exc.NotFoundException('Transaction not found')
    request_data = json.loads(request_data)
    device, counter, presence = complete_authentication(
        request_data, sign_response, client.valid_facets)
    dev = user.devices[request_data['handleMap'][device['keyHandle']]]
    if dev.compromised:
        raise exc.DeviceCompromisedException('Device is compromised',
                                             dev.get_descriptor())
    if presence == 0:
        raise exc.BadInputException('User presence byte not set')
    if counter > (dev.counter or -1):
        dev.counter = counter
        dev.authenticated_at = datetime.now()
        dev.update_properties(request_data['properties'])
        dev.update_properties(response_data.properties)
        db.session.commit()
        return dev.get_descriptor(get_metadata(dev))
    else:
        dev.compromised = True
        db.session.commit()
        raise exc.DeviceCompromisedException('Device counter mismatch',
                                             dev.get_descriptor())
Beispiel #12
0
def admin_login() -> _Response:
    if session.get("logged_in") is True:
        return redirect(url_for("admin.admin_notifications"))

    devices = [doc["device"] for doc in DB.u2f.find()]
    u2f_enabled = True if devices else False
    if request.method == "POST":
        csrf.protect()
        # 1. Check regular password login flow
        pwd = request.form.get("pass")
        if pwd:
            if verify_pass(pwd):
                session.permanent = True
                session["logged_in"] = True
                return redirect(
                    request.args.get("redirect")
                    or url_for("admin.admin_notifications"))
            else:
                abort(403)
        # 2. Check for U2F payload, if any
        elif devices:
            resp = json.loads(request.form.get("resp"))  # type: ignore
            try:
                u2f.complete_authentication(session["challenge"], resp)
            except ValueError as exc:
                print("failed", exc)
                abort(403)
                return
            finally:
                session["challenge"] = None

            session.permanent = True
            session["logged_in"] = True
            return redirect(
                request.args.get("redirect")
                or url_for("admin.admin_notifications"))
        else:
            abort(401)

    payload = None
    if devices:
        payload = u2f.begin_authentication(ID, devices)
        session["challenge"] = payload

    return htmlify(
        render_template("login.html", u2f_enabled=u2f_enabled,
                        payload=payload))
Beispiel #13
0
 def clean(self):
     cleaned_data = super().clean()
     u2f_challenge = self.session["u2f_challenge"]
     token_response = cleaned_data["token_response"]
     device, _, _ = complete_authentication(u2f_challenge, token_response,
                                            [zentral_settings["api"]["tls_hostname"]])
     if not device:
         raise forms.ValidationError("Could not complete the U2F authentication")
     return cleaned_data
    def verify(self, username, data):
        user = self.users[username]

        challenge = user.pop('_u2f_challenge_')
        device, c, t = complete_authentication(challenge, data, [self.facet])
        return json.dumps({
            'keyHandle': device['keyHandle'],
            'touch': t,
            'counter': c
        })
Beispiel #15
0
    def verify(self, username, data):
        user = self.users[username]

        challenge = user.pop('_u2f_challenge_')
        device, c, t = complete_authentication(challenge, data, [self.facet])
        return json.dumps({
            'keyHandle': device['keyHandle'],
            'touch': t,
            'counter': c
        })
 def clean(self):
     if isinstance(self.initial_device, U2FDevice):
         try:
             response = json.loads(self.cleaned_data['otp_token'])
             request = self.request.session['u2f_sign_request']
             device, login_counter, _ = u2f.complete_authentication(request, response)
         except (ValueError, TypeError):
             self.add_error('__all__', 'U2F validation failed -- bad signature.')
     else:
         self.clean_otp(self.user)
     return self.cleaned_data
def verify():
    app_id = get_origin(request.environ)
    username = request.args.get('username', 'user')
    data = request.data.decode()
    user = users[username]
    challenge = user.pop('_u2f_challenge_')
    device, c, t = complete_authentication(challenge, data, [app_id])
    return json.dumps({
        'keyHandle': device['keyHandle'],
        'touch': t,
        'counter': c
    })
Beispiel #18
0
    def post(self, request, *args, **kwargs):
        data = json.loads(request.POST["response"])
        if data.get("errorCode", 0) != 0:
            messages.error(request, "Invalid security key response.")
            return super().get(request, *args, **kwargs)

        challenge = request.session.pop('_u2f_challenge_')
        device, c, t = u2f.complete_authentication(challenge, data, [mf_settings['U2F_APPID']])

        key = self.keys.get(properties__device__publicKey=device["publicKey"])
        write_session(request, key)
        return login(request)
Beispiel #19
0
 def validate_second_factor(self):
     response = json.loads(self.cleaned_data['response'])
     try:
         device, login_counter, _ = u2f.complete_authentication(self.sign_request, response)
         # TODO: store login_counter and verify it's increasing
         device = self.user.u2f_keys.get(key_handle=device['keyHandle'])
         device.last_used_at = timezone.now()
         device.save()
         del self.request.session['u2f_sign_request']
         return True
     except ValueError:
         self.add_error('__all__', 'U2F validation failed -- bad signature.')
     return False
Beispiel #20
0
 def validate_response(self, request: Request, challenge, response, has_webauthn_register):
     try:
         if not has_webauthn_register:
             u2f.complete_authentication(challenge, response, self.u2f_facets)
             return True
         credentials = []
         for device in self.get_u2f_devices():
             if type(device) == AuthenticatorData:
                 credentials.append(device.credential_data)
             else:
                 credentials.append(create_credential_object(device))
         self.webauthn_authentication_server.authenticate_complete(
             state=request.session["webauthn_authentication_state"],
             credentials=credentials,
             credential_id=websafe_decode(response["keyHandle"]),
             client_data=ClientData(websafe_decode(response["clientData"])),
             auth_data=AuthenticatorData(websafe_decode(response["authenticatorData"])),
             signature=websafe_decode(response["signatureData"]),
         )
     except (InvalidSignature, InvalidKey, StopIteration):
         return False
     return True
Beispiel #21
0
def verify(user, key_handle, signature_data, client_data):
    challenge = session.pop('_u2f_challenge_')
    if not challenge:
        current_app.logger.error('Found no U2F challenge data in session.')
        return {'_error': True, 'message': 'security.u2f.missing_challenge_data'}
    data = {
        'keyHandle': key_handle,
        'signatureData': signature_data,
        'clientData': client_data
    }
    device, c, t = complete_authentication(challenge, data, current_app.config['U2F_FACETS'])
    current_app.stats.count(name='u2f_verify')
    return {'key_handle': device['keyHandle'], 'counter': c, 'touch': t}
Beispiel #22
0
 def perform_verify(self, cleaned_data):
     challenge = cleaned_data['u2f_challenge_signed']
     data = cleaned_data['u2f_verify']
     try:
         device, c, t = complete_authentication(challenge, data, [APP_ID])
     except ValueError as e:
         raise forms.ValidationError("Authentication failed. {}.".format(
             str(e)))
     cleaned_data.update({
         'keyHandle': device['keyHandle'],
         'touch': t,
         'counter': c,
         'token': self.get_token(device)
     })
Beispiel #23
0
    def u2f_check_credentials(self, last_challenge, last_response):
        if self._u2f_get_device():
            baseurl = self.env['ir.config_parameter'].sudo().get_param(
                'web.base.url')
            try:
                device, c, t = complete_authentication(last_challenge,
                                                       last_response,
                                                       [baseurl])
            except Exception:
                _logger.info('Exception during U2F authentication.',
                             exc_info=True)
                raise U2FAuthenticationError()

            _logger.debug('Successful U2F auth with: %s, %s, %s', device, c, t)
        return True
Beispiel #24
0
def verify(user, key_handle, signature_data, client_data):
    challenge = session.pop('_u2f_challenge_')
    if not challenge:
        current_app.logger.error('Found no U2F challenge data in session.')
        return error_response(message=SecurityMsg.no_challenge)

    data = {
        'keyHandle': key_handle,
        'signatureData': signature_data,
        'clientData': client_data
    }
    device, c, t = complete_authentication(challenge, data,
                                           current_app.config.u2f_facets)
    current_app.stats.count(name='u2f_verify')
    return {'key_handle': device['keyHandle'], 'counter': c, 'touch': t}
Beispiel #25
0
 def validate_second_factor(self):
     response = json.loads(self.cleaned_data['response'])
     try:
         device, login_counter, _ = u2f.complete_authentication(
             self.sign_request, response)
         # TODO: store login_counter and verify it's increasing
         device = self.user.u2f_keys.get(key_handle=device['keyHandle'])
         device.last_used_at = timezone.now()
         device.save()
         del self.request.session['u2f_sign_request']
         return True
     except ValueError:
         self.add_error('__all__',
                        'U2F validation failed -- bad signature.')
     return False
Beispiel #26
0
    def authenticate(self, request, user, u2f_request, u2f_response):
        """Authenticate using U2F."""
        try:
            device, counter, user_presence = u2f.complete_authentication(u2f_request, u2f_response)
        except (TypeError, ValueError, KeyError) as error:
            _LOGGER.info("U2F authentication failed with error: %r", error)
            return None

        u2f_device = user.u2f_devices.get(key_handle=device['keyHandle'])
        try:
            self.mark_device_used(u2f_device, counter)
        except ValueError:
            # Raise `PermissionDenied` to stop the authentication process and skip remaining backends.
            messages.error(request, self.counter_error_message)
            raise PermissionDenied("Counter didn't increase.")
        return user
Beispiel #27
0
def finish_login(request):
    username = request.data.get('username')
    response = request.data.get('response')

    user = get_object_or_404(User, username=username)
    auth_req = get_object_or_404(U2FAuthenticationRequest, user=user)

    json_auth_req = json.loads(auth_req.body)

    try:
        device, counter, _ = u2f.complete_authentication(
            json_auth_req, response)
        # TODO: store login_counter and verify it's increasing
        device = user.u2f_key.get(keyHandle=device['keyHandle'])
        device.last_used_at = timezone.now()
        device.save()
        auth_req.delete()
    except ValueError:
        return Response({"error": "U2F validation failed -- bad signature"}, status=HTTP_401_UNAUTHORIZED)

    token, _ = Token.objects.get_or_create(user=user)
    return Response({"token": token.key})
Beispiel #28
0
    def perform_step(self, action):
        current_app.logger.debug('Performing MFA step')
        if current_app.config['MFA_TESTING']:
            current_app.logger.debug('Test mode is on, faking authentication')
            return {
                'success': True,
                'testing': True,
            }

        if action.old_format:
            userid = action.user_id
            user = current_app.central_userdb.get_user_by_id(
                userid, raise_on_missing=False)
        else:
            eppn = action.eppn
            user = current_app.central_userdb.get_user_by_eppn(
                eppn, raise_on_missing=False)
        current_app.logger.debug(
            'Loaded User {} from db (in perform_action)'.format(user))

        # Third party service MFA
        if session.mfa_action.success is True:  # Explicit check that success is the boolean True
            issuer = session.mfa_action.issuer
            authn_instant = session.mfa_action.authn_instant
            authn_context = session.mfa_action.authn_context
            current_app.logger.info(
                'User {} logged in using external mfa service {}'.format(
                    user, issuer))
            action.result = {
                'success': True,
                'issuer': issuer,
                'authn_instant': authn_instant,
                'authn_context': authn_context
            }
            current_app.actions_db.update_action(action)
            return action.result

        req_json = request.get_json()
        if not req_json:
            current_app.logger.error(
                'No data in request to authn {}'.format(user))
            raise self.ActionError('mfa.no-request-data')

        # Process POSTed data
        if 'tokenResponse' in req_json:
            # CTAP1/U2F
            token_response = request.get_json().get('tokenResponse', '')
            current_app.logger.debug(
                'U2F token response: {}'.format(token_response))

            challenge = session.get(self.PACKAGE_NAME + '.u2f.challenge')
            current_app.logger.debug('Challenge: {!r}'.format(challenge))

            device, counter, touch = complete_authentication(
                challenge, token_response,
                current_app.config['U2F_VALID_FACETS'])
            current_app.logger.debug('U2F authentication data: {}'.format({
                'keyHandle':
                device['keyHandle'],
                'touch':
                touch,
                'counter':
                counter,
            }))

            for this in user.credentials.filter(U2F).to_list():
                if this.keyhandle == device['keyHandle']:
                    current_app.logger.info(
                        'User {} logged in using U2F token {} (touch: {}, counter {})'
                        .format(user, this, touch, counter))
                    action.result = {
                        'success': True,
                        'touch': touch,
                        'counter': counter,
                        RESULT_CREDENTIAL_KEY_NAME: this.key,
                    }
                    current_app.actions_db.update_action(action)
                    return action.result
        elif 'authenticatorData' in req_json:
            # CTAP2/Webauthn
            req = {}
            for this in [
                    'credentialId', 'clientDataJSON', 'authenticatorData',
                    'signature'
            ]:
                try:
                    req_json[this] += ('=' * (len(req_json[this]) % 4))
                    req[this] = base64.urlsafe_b64decode(req_json[this])
                except:
                    current_app.logger.error(
                        'Failed to find/b64decode Webauthn parameter {}: {}'.
                        format(this, req_json.get(this)))
                    raise self.ActionError(
                        'mfa.bad-token-response'
                    )  # XXX add bad-token-response to frontend
            current_app.logger.debug(
                'Webauthn request after decoding:\n{}'.format(
                    pprint.pformat(req)))
            client_data = ClientData(req['clientDataJSON'])
            auth_data = AuthenticatorData(req['authenticatorData'])

            credentials = _get_user_credentials(user)
            fido2state = json.loads(session[self.PACKAGE_NAME +
                                            '.webauthn.state'])

            rp_id = current_app.config['FIDO2_RP_ID']
            fido2rp = RelyingParty(rp_id, 'eduID')
            fido2server = _get_fido2server(credentials, fido2rp)
            matching_credentials = [
                (v['webauthn'], k) for k, v in credentials.items()
                if v['webauthn'].credential_id == req['credentialId']
            ]

            if not matching_credentials:
                current_app.logger.error(
                    'Could not find webauthn credential {} on user {}'.format(
                        req['credentialId'], user))
                raise self.ActionError('mfa.unknown-token')

            authn_cred = fido2server.authenticate_complete(
                fido2state,
                [mc[0] for mc in matching_credentials],
                req['credentialId'],
                client_data,
                auth_data,
                req['signature'],
            )
            current_app.logger.debug(
                'Authenticated Webauthn credential: {}'.format(authn_cred))

            cred_key = [mc[1] for mc in matching_credentials][0]

            touch = auth_data.flags
            counter = auth_data.counter
            current_app.logger.info(
                'User {} logged in using Webauthn token {} (touch: {}, counter {})'
                .format(user, cred_key, touch, counter))
            action.result = {
                'success':
                True,
                'touch':
                auth_data.is_user_present() or auth_data.is_user_verified(),
                'user_present':
                auth_data.is_user_present(),
                'user_verified':
                auth_data.is_user_verified(),
                'counter':
                counter,
                RESULT_CREDENTIAL_KEY_NAME:
                cred_key,
            }
            current_app.actions_db.update_action(action)
            return action.result

        else:
            current_app.logger.error(
                'Neither U2F nor Webauthn data in request to authn {}'.format(
                    user))
            current_app.logger.debug('Request: {}'.format(req_json))
            raise self.ActionError('mfa.no-token-response')

        raise self.ActionError('mfa.unknown-token')
Beispiel #29
0
def _complete_authentication(request, u2f_key):
  u2f_response = request.POST['response']
  device, login_counter, _ = u2f.complete_authentication(
    request.session['u2f_request'], u2f_response)
  u2f_key.last_used_at = timezone.now()
  u2f_key.save()
Beispiel #30
0
 def validate_response(self, request, challenge, response):
     try:
         u2f.complete_authentication(challenge, response, self.u2f_facets)
     except (InvalidSignature, InvalidKey, StopIteration):
         return False
     return True
Beispiel #31
0
 def validate_response(self, request, challenge, response):
     try:
         u2f.complete_authentication(challenge, response, self.u2f_facets)
     except (InvalidSignature, InvalidKey, StopIteration):
         return False
     return True
Beispiel #32
0
    def perform_step(self, action):
        current_app.logger.debug('Performing MFA step')
        if current_app.config['MFA_TESTING']:
            current_app.logger.debug('Test mode is on, faking authentication')
            return {
                'success': True,
                'testing': True,
            }

        if action.old_format:
            userid = action.user_id
            user = current_app.central_userdb.get_user_by_id(userid, raise_on_missing=False)
        else:
            eppn = action.eppn
            user = current_app.central_userdb.get_user_by_eppn(eppn, raise_on_missing=False)
        current_app.logger.debug('Loaded User {} from db (in perform_action)'.format(user))

        # Third party service MFA
        if session.mfa_action.success is True:  # Explicit check that success is the boolean True
            issuer = session.mfa_action.issuer
            authn_instant = session.mfa_action.authn_instant
            authn_context = session.mfa_action.authn_context
            current_app.logger.info('User {} logged in using external mfa service {}'.format(user, issuer))
            action.result = {
                'success': True,
                'issuer': issuer,
                'authn_instant': authn_instant,
                'authn_context': authn_context
            }
            current_app.actions_db.update_action(action)
            return action.result

        req_json = request.get_json()
        if not req_json:
            current_app.logger.error('No data in request to authn {}'.format(user))
            raise self.ActionError('mfa.no-request-data')

        # Process POSTed data
        if 'tokenResponse' in req_json:
            # CTAP1/U2F
            token_response = request.get_json().get('tokenResponse', '')
            current_app.logger.debug('U2F token response: {}'.format(token_response))

            challenge = session.get(self.PACKAGE_NAME + '.u2f.challenge')
            current_app.logger.debug('Challenge: {!r}'.format(challenge))

            device, counter, touch = complete_authentication(challenge, token_response,
                                                             current_app.config['U2F_VALID_FACETS'])
            current_app.logger.debug('U2F authentication data: {}'.format({
                'keyHandle': device['keyHandle'],
                'touch': touch,
                'counter': counter,
            }))

            for this in user.credentials.filter(U2F).to_list():
                if this.keyhandle == device['keyHandle']:
                    current_app.logger.info('User {} logged in using U2F token {} (touch: {}, counter {})'.format(
                        user, this, touch, counter))
                    action.result = {'success': True,
                                     'touch': touch,
                                     'counter': counter,
                                     RESULT_CREDENTIAL_KEY_NAME: this.key,
                                     }
                    current_app.actions_db.update_action(action)
                    return action.result
        elif 'authenticatorData' in req_json:
            # CTAP2/Webauthn
            req = {}
            for this in ['credentialId', 'clientDataJSON', 'authenticatorData', 'signature']:
                try:
                    req_json[this] += ('=' * (len(req_json[this]) % 4))
                    req[this] = base64.urlsafe_b64decode(req_json[this])
                except:
                    current_app.logger.error('Failed to find/b64decode Webauthn parameter {}: {}'.format(
                        this, req_json.get(this)))
                    raise self.ActionError('mfa.bad-token-response')  # XXX add bad-token-response to frontend
            current_app.logger.debug('Webauthn request after decoding:\n{}'.format(pprint.pformat(req)))
            client_data = ClientData(req['clientDataJSON'])
            auth_data = AuthenticatorData(req['authenticatorData'])

            credentials = _get_user_credentials(user)
            fido2state = json.loads(session[self.PACKAGE_NAME + '.webauthn.state'])

            rp_id = current_app.config['FIDO2_RP_ID']
            fido2rp = RelyingParty(rp_id, 'eduID')
            fido2server = _get_fido2server(credentials, fido2rp)
            matching_credentials = [(v['webauthn'], k) for k,v in credentials.items()
                                    if v['webauthn'].credential_id == req['credentialId']]

            if not matching_credentials:
                current_app.logger.error('Could not find webauthn credential {} on user {}'.format(
                    req['credentialId'], user))
                raise self.ActionError('mfa.unknown-token')

            authn_cred = fido2server.authenticate_complete(
                fido2state,
                [mc[0] for mc in matching_credentials],
                req['credentialId'],
                client_data,
                auth_data,
                req['signature'],
            )
            current_app.logger.debug('Authenticated Webauthn credential: {}'.format(authn_cred))

            cred_key = [mc[1] for mc in matching_credentials][0]

            touch = auth_data.flags
            counter = auth_data.counter
            current_app.logger.info('User {} logged in using Webauthn token {} (touch: {}, counter {})'.format(
                user, cred_key, touch, counter))
            action.result = {'success': True,
                             'touch': auth_data.is_user_present() or auth_data.is_user_verified(),
                             'user_present': auth_data.is_user_present(),
                             'user_verified': auth_data.is_user_verified(),
                             'counter': counter,
                             RESULT_CREDENTIAL_KEY_NAME: cred_key,
                             }
            current_app.actions_db.update_action(action)
            return action.result

        else:
            current_app.logger.error('Neither U2F nor Webauthn data in request to authn {}'.format(user))
            current_app.logger.debug('Request: {}'.format(req_json))
            raise self.ActionError('mfa.no-token-response')

        raise self.ActionError('mfa.unknown-token')