As of November 2022, Twilio no longer provides support for Authy SMS/Voice-only customers. Customers who were also using Authy TOTP or Push prior to March 1, 2023 are still supported. The Authy API is now closed to new customers and will be fully deprecated in the future.
For new development, we encourage you to use the Verify v2 API.
Existing customers will not be impacted at this time until Authy API has reached End of Life. For more information about migration, see Migrating from Authy to Verify for SMS.
Adding Two-factor Authentication to your application is the easiest way to increase security and trust in your product without unnecessarily burdening your users. This quickstart guides you through building a Python and Flask application that restricts access to a URL. Four Two-factor Authentication channels are demoed: SMS, Voice, Soft Tokens and Push Notifications.
Ready to protect your toy app's users from nefarious balaclava wearing hackers? Dive in!
Create a new Twilio account (you can sign up for a free Twilio trial), or sign into an existing Twilio account.
Once logged in, visit the Authy Console. Click on the red 'Create New Aplication' (or big red plus ('+') if you already created one) to create a new Authy application then name it something memorable.
You'll automatically be transported to the Settings page next. Click the eyeball icon to reveal your Production API Key.
Copy your Production API Key to a safe place, you will use it during application setup.
This Two-factor Authentication demos two channels which require an installed Authy Client to test: Soft Tokens and Push Notifications. While SMS and Voice channels will work without the client, to try out all four authentication channels download and install Authy Client for Desktop or Mobile:
Clone our repository locally, then enter the directory. Install all of the necessary python modules:
_10pipenv install
or
_10pip -r requirements.txt
Next, open the file .env.example
. There, edit the ACCOUNT_SECURITY_API_KEY
, pasting in the API Key from the above step (in the console), and save the file as .env
.
Enter the API Key from the Account Security console and optionally change the port.
_10SECRET_KEY=[create_a_key]_10_10DATABASE_URI=_10_10# You can get/create one here :_10# https://www.twilio.com/console/authy/applications_10ACCOUNT_SECURITY_API_KEY='ENTER_SECRET_HERE'
Once you have added your API Key, you are ready to run! Launch Flask with:
_10./manage.py runserver
If your API Key is correct, you should get a message your new app is running!
With your phone (optionally with the Authy client installed) nearby, open a new browser tab and navigate to http://localhost:5000/register/
Enter your information and invent a password, then hit 'Register'. Your information is passed to Twilio (you will be able to see your user immediately in the console), and the application is returned a user_id
.
Now visit http://localhost:5000/login/ and login. You'll be presented with a happy screen:
If your phone has the Authy Client installed, you can immediately enter a Soft Token from the client to Verify. Additionally, you can try a Push Notification simply by pushing the labeled button.
If you do not have the Authy Client installed, the SMS and Voice channels will also work in providing a token. To try different channels, you can logout to start the process again.
_208import flask_208_208from authy.api import AuthyApiClient_208from flask_login import login_required, login_user, logout_user, current_user_208_208from . import app, login_manager, db_208# from .database import db_session_208from .decorators import twofa_required_208from .forms import (_208 LoginForm,_208 RegistrationForm,_208 TokenVerificationForm,_208 PhoneVerificationForm,_208 TokenPhoneValidationForm,_208)_208from .models import User_208_208_208authy_api = AuthyApiClient(app.config.get('ACCOUNT_SECURITY_API_KEY'))_208_208_208@app.route('/protected')_208@login_required_208@twofa_required_208def protected():_208 return flask.render_template('protected.html')_208_208_208@app.route('/login', methods=['GET', 'POST'])_208def login():_208 form = LoginForm()_208 if form.validate_on_submit():_208 login_user(form.user, remember=True)_208 form.user.is_authenticated = True_208 db.session.add(form.user)_208 db.session.commit()_208 next = flask.request.args.get('next')_208 return flask.redirect(next or flask.url_for('protected'))_208 return flask.render_template('login.html', form=form)_208_208_208@app.route("/logout", methods=["GET"])_208@login_required_208def logout():_208 current_user.is_authenticated = False_208 db.session.add(current_user)_208 db.session.commit()_208 flask.session['authy'] = False_208 flask.session['is_verified'] = False_208 logout_user()_208 return flask.redirect('/login')_208_208_208@app.route('/', methods=['GET', 'POST'])_208def index():_208 return flask.redirect('/login')_208_208_208login_manager.unauthorized_handler(index)_208_208_208@app.route('/register', methods=['GET', 'POST'])_208def register():_208 form = RegistrationForm()_208 if form.validate_on_submit():_208 authy_user = authy_api.users.create(_208 form.email.data,_208 form.phone_number.data,_208 form.country_code.data,_208 )_208 if authy_user.ok():_208 user = User(_208 form.username.data,_208 form.email.data,_208 form.password.data,_208 authy_user.id,_208 is_authenticated=True_208 )_208 user.authy_id = authy_user.id_208 db.session.add(user)_208 db.session.commit()_208 login_user(user, remember=True)_208 return flask.redirect('/protected')_208 else:_208 form.errors['non_field'] = []_208 for key, value in authy_user.errors().items():_208 form.errors['non_field'].append(_208 '{key}: {value}'.format(key=key, value=value)_208 )_208 return flask.render_template('register.html', form=form)_208_208_208@app.route('/2fa', methods=['GET', 'POST'])_208@login_required_208def twofa():_208 form = TokenVerificationForm(current_user.authy_id)_208 if form.validate_on_submit():_208 flask.session['authy'] = True_208 return flask.redirect('/protected')_208 return flask.render_template('2fa.html', form=form)_208_208_208@app.route('/token/sms', methods=['POST'])_208@login_required_208def token_sms():_208 sms = authy_api.users.request_sms(current_user.authy_id, {'force': True})_208 if sms.ok():_208 return flask.Response('SMS request successful', status=200)_208 else:_208 return flask.Response('SMS request failed', status=503)_208_208_208@app.route('/token/voice', methods=['POST'])_208@login_required_208def token_voice():_208 call = authy_api.users.request_call(current_user.authy_id, {'force': True})_208 if call.ok():_208 return flask.Response('Call request successful', status=200)_208 else:_208 return flask.Response('Call request failed', status=503)_208_208_208@app.route('/token/onetouch', methods=['POST'])_208@login_required_208def token_onetouch():_208 details = {_208 'Authy ID': current_user.authy_id,_208 'Username': current_user.username,_208 'Reason': 'Demo by Account Security'_208 }_208_208 hidden_details = {_208 'test': 'This is a'_208 }_208_208 response = authy_api.one_touch.send_request(_208 int(current_user.authy_id),_208 message='Login requested for Account Security account.',_208 seconds_to_expire=120,_208 details=details,_208 hidden_details=hidden_details_208 )_208 if response.ok():_208 flask.session['onetouch_uuid'] = response.get_uuid()_208 return flask.Response('OneTouch request successfull', status=200)_208 else:_208 return flask.Response('OneTouch request failed', status=503)_208_208_208@app.route('/onetouch-status', methods=['POST'])_208@login_required_208def onetouch_status():_208 uuid = flask.session['onetouch_uuid']_208 approval_status = authy_api.one_touch.get_approval_status(uuid)_208 if approval_status.ok():_208 if approval_status['approval_request']['status'] == 'approved':_208 flask.session['authy'] = True_208 return flask.Response(_208 approval_status['approval_request']['status'],_208 status=200_208 )_208 else:_208 return flask.Response(approval_status.errros(), status=503)_208_208_208######################_208# Phone Verification #_208######################_208_208@app.route('/verification', methods=['GET', 'POST'])_208def phone_verification():_208 form = PhoneVerificationForm()_208 if form.validate_on_submit():_208 flask.session['phone_number'] = form.phone_number.data_208 flask.session['country_code'] = form.country_code.data_208 authy_api.phones.verification_start(_208 form.phone_number.data,_208 form.country_code.data,_208 via=form.via.data_208 )_208 return flask.redirect('/verification/token')_208 return flask.render_template('phone_verification.html', form=form)_208_208_208@app.route('/verification/token', methods=['GET', 'POST'])_208def token_validation():_208 form = TokenPhoneValidationForm()_208 if form.validate_on_submit():_208 verification = authy_api.phones.verification_check(_208 flask.session['phone_number'],_208 flask.session['country_code'],_208 form.token.data_208 )_208 if verification.ok():_208 flask.session['is_verified'] = True_208 return flask.redirect('/verified')_208 else:_208 form.errors['non_field'] = []_208 for error_msg in verification.errors().values():_208 form.errors['non_field'].append(error_msg)_208 return flask.render_template('token_validation.html', form=form)_208_208_208@app.route('/verified')_208def verified():_208 if not flask.session.get('is_verified'):_208 return flask.redirect('/verification')_208 return flask.render_template('verified.html')
And there you go, Two-factor Authentication is on and your Flask app is protected!
Now that you are keeping the hackers out of this demo app using Two-factor Authentication, you can find all of the detailed descriptions for options and API calls in our Two-factor Authentication API Reference. If you're also building a registration flow, also check out our Phone Verification product and the Verification Quickstart which uses this codebase.
For additional guides and tutorials on account security and other products, in Python and in our other languages, take a look at the Docs.