Flask-OpenID¶
Flask-OpenID is an extension to Flask that allows you to add OpenID based authentication to your website in a matter of minutes. It depends on Flask and python-openid 2.x. You can install the requirements from PyPI with easy_install or pip or download them by hand.
Features¶
support for OpenID 2.x
friendly API
perfect integration into Flask
basic support for AX and SReg extensions to OpenID that make it possible to fetch basic profile information from a user’s OpenID provider.
Installation¶
Install the extension with one of the following commands:
$ easy_install Flask-OpenID
or alternatively if you have pip installed:
$ pip install Flask-OpenID
How to Use¶
To integrate Flask-OpenID into your application you need to create an
instance of the OpenID
object first:
from flask.ext.openid import OpenID
oid = OpenID(app, '/path/to/store', safe_roots=[])
By default it will use the filesystem as store for information needed by OpenID for the authentication process. You can alternatively implement your own store that uses the database or a no-sql server. For more information about that, consult the python-openid documentation.
The path to the store can also be specified with the
OPENID_FS_STORE_PATH
configuration variable.
Alternatively the object can be instantiated without the application in
which case it can later be registered for an application with the
init_app()
method.
The list of URL roots that are safe to redirect the user to are passed via
safe_roots. Whenever the url root of the 'next'
request argument is not in
this list, the user will get redirected to the app root. All urls that are local
to the current app are always regared as trusted. This security mechanism
can be disabled by leaving safe_roots out, but this is not suggested.
The current logged in user has to be memorized somewhere, we will use the
'openid'
key in the session. This can be implemented in a
before_request function:
from flask import g, session
@app.before_request
def lookup_current_user():
g.user = None
if 'openid' in session:
openid = session['openid']
g.user = User.query.filter_by(openid=openid).first()
This assumes the openid used for a user is stored in the user table itself. As you can see from the example above, we’re using SQLAlchemy here, but feel free to use a different storage backend. It’s just important that you can somehow map from openid URL to user.
Next you need to define a login handling function. This function is a
standard view function that is additionally decorated as
loginhandler()
:
@app.route('/login', methods=['GET', 'POST'])
@oid.loginhandler
def login():
if g.user is not None:
return redirect(oid.get_next_url())
if request.method == 'POST':
openid = request.form.get('openid')
if openid:
return oid.try_login(openid, ask_for=['email', 'nickname'],
ask_for_optional=['fullname'])
return render_template('login.html', next=oid.get_next_url(),
error=oid.fetch_error())
What’s happening inside the login handler is that first we try to figure
out if the user is already logged in. In that case we return to where we
just came from (get_next_url()
can do that for us). When
the data is submitted we get the openid the user entered and try to login
with that information. Additionally we ask the openid provider for email,
nickname and the user’s full name, where we declare full name as optional.
If that information is available, we can use it to simplify the account
creation process in our application.
The template also needs the URL we want to return to, because it has to
forward that information in the form. If an error happened,
fetch_error()
will return that error message for us.
This is what a login template typically looks like:
{% extends "layout.html" %}
{% block title %}Sign in{% endblock %}
{% block body %}
<h2>Sign in</h2>
<form action="" method=post>
{% if error %}<p class=error><strong>Error:</strong> {{ error }}</p>{% endif %}
<p>
OpenID:
<input type=text name=openid size=30>
<input type=submit value="Sign in">
<input type=hidden name=next value="{{ next }}">
</form>
{% endblock %}
See how error and next are used. The name of the form field next is required, so don’t change it.
Responding to Successful Logins¶
Next we have to define a function that is called after the login was successful. The responsibility of that function is to remember the user that just logged in and to figure out if it’s a new user to the system or one with an existing profile (if you want to use profiles).
Such a function is decorated with after_login()
and must
remember the user in the session and redirect to the proper page:
from flask import flash
@oid.after_login
def create_or_login(resp):
session['openid'] = resp.identity_url
user = User.query.filter_by(openid=resp.identity_url).first()
if user is not None:
flash(u'Successfully signed in')
g.user = user
return redirect(oid.get_next_url())
return redirect(url_for('create_profile', next=oid.get_next_url(),
name=resp.fullname or resp.nickname,
email=resp.email))
The resp object passed is a OpenIDResponse
object with all the
information you might desire. As you can see, we memorize the user’s
openid and try to get the user with that OpenID from the database. If
that fails we redirect the user to a page to create a new profile and also
forward the name (or nickname if no name is provided) and the email
address. Please keep in mind that an openid provider does not have to
support these profile information and not every value you ask for will be
there. If it’s missing it will be None. Again make sure to not lose
the information about the next URL.
Creating a Profile¶
A typical page to create such a profile might look like this:
@app.route('/create-profile', methods=['GET', 'POST'])
def create_profile():
if g.user is not None or 'openid' not in session:
return redirect(url_for('index'))
if request.method == 'POST':
name = request.form['name']
email = request.form['email']
if not name:
flash(u'Error: you have to provide a name')
elif '@' not in email:
flash(u'Error: you have to enter a valid email address')
else:
flash(u'Profile successfully created')
db_session.add(User(name, email, session['openid']))
db_session.commit()
return redirect(oid.get_next_url())
return render_template('create_profile.html', next=oid.get_next_url())
If you’re using the same names for the URL parameters in the step before and in this form, you have nice looking and simple templates:
{% extends "layout.html" %}
{% block title %}Create Profile{% endblock %}
{% block body %}
<h2>Create Profile</h2>
<p>
Hey! This is the first time you signed in on this website. In
order to proceed we need a couple of more information from you:
<form action="" method=post>
<dl>
<dt>Name:
<dd><input type=text name=name size=30 value="{{ request.values.name }}">
<dt>E-Mail:
<dd><input type=text name=email size=30 value="{{ request.values.email }}">
</dl>
<p>
<input type=submit value="Create profile">
<input type=hidden name=next value="{{ next }}">
</form>
<p>
If you don't want to proceed, you can <a href="{{ url_for('logout')
}}">sign out</a> again.
{% endblock %}
Logging Out¶
The logout function is very simple, it just has to unset the openid from the session and redirect back to where the user was before:
@app.route('/logout')
def logout():
session.pop('openid', None)
flash(u'You were signed out')
return redirect(oid.get_next_url())
Advanced usage¶
Flask-OpenID can also work with any python-openid extension.
To use this, pass a list of instantiated request openid.extension.Extension
objects in the extensions field of try_login()
.
The responses of these extensions are available during the after_login()
function, as entries in resp.extensions.
Full Example¶
To see the full code of that example, you can download the code from github.
Changes¶
1.2¶
The safe_roots argument and URL security system was added.
The OpenID extensions system was added.
1.0¶
the OpenID object is not registered to an application which allows configuration values to be used and is also consistent with other Flask extensions.
API References¶
The full API reference:
- class flask_openid.OpenID(app=None, fs_store_path=None, store_factory=None, fallback_endpoint=None, extension_responses=None, safe_roots=None, url_root_as_trust_root=False)¶
Simple helper class for OpenID auth. Has to be created in advance like a
Flask
object.There are two usage modes which work very similar. One is binding the instance to a very specific Flask application:
app = Flask(__name__) db = OpenID(app)
The second possibility is to create the object once and configure the application later to support it:
oid = OpenID() def create_app(): app = Flask(__name__) oid.init_app(app) return app
- Parameters
app – the application to register this openid controller with.
fs_store_path – if given this is the name of a folder where the OpenID auth process can store temporary information. If neither is provided a temporary folder is assumed. This is overridden by the
OPENID_FS_STORE_PATH
configuration key.store_factory – alternatively a function that creates a python-openid store object.
fallback_endpoint – optionally a string with the name of an URL endpoint the user should be redirected to if the HTTP referrer is unreliable. By default the user is redirected back to the application’s index in that case.
extension_responses – a list of OpenID Extensions Response class.
safe_roots – a list of trust roots to support returning to
url_root_as_trust_root – whether to use the url_root as trust_root
- after_login(f)¶
This function will be called after login. It must redirect to a different place and remember the user somewhere. The session is not modified by SimpleOpenID. The decorated function is passed a
OpenIDResponse
object.
- attach_reg_info(auth_request, keys, optional_keys)¶
Attaches sreg and ax requests to the auth request.
- Internal
- errorhandler(f)¶
Called if an error occurs with the message. By default
'openid_error'
is added to the session so thatfetch_error()
can fetch that error from the session. Alternatively it makes sense to directly flash the error for example:@oid.errorhandler def on_error(message): flash(u'Error: ' + message)
- fetch_error()¶
Fetches the error from the session. This removes it from the session and returns that error. This method is probably useless if
errorhandler()
is used.
- get_current_url()¶
the current URL + next.
- get_next_url()¶
Returns the URL where we want to redirect to. This will always return a valid URL.
- get_success_url()¶
Return the internal success URL.
- Internal
- init_app(app)¶
This callback can be used to initialize an application for the use with this openid controller.
New in version 1.0.
- loginhandler(f)¶
Marks a function as login handler. This decorator injects some more OpenID required logic. Always decorate your login function with this decorator.
- signal_error(msg)¶
Signals an error. It does this by storing the message in the session. Use
errorhandler()
to this method.
- try_login(identity_url, ask_for=None, ask_for_optional=None, extensions=None, immediate=False)¶
This tries to login with the given identity URL. This function must be called from the login_handler. The ask_for and ask_for_optional`parameter can be a set of values to be asked from the openid provider, where keys in `ask_for are marked as required, and keys in ask_for_optional are marked as optional.
The following strings can be used in the ask_for and ask_for_optional parameters:
aim
,blog
,country
,dob
(date of birth),email
,fullname
,gender
,icq
,image
,jabber
,language
,msn
,nickname
,phone
,postcode
,skype
,timezone
,website
,yahoo
extensions can be a list of instances of OpenID extension requests that should be passed on with the request. If you use this, please make sure to pass the Response classes of these extensions when initializing OpenID.
immediate can be used to indicate this request should be a so-called checkid_immediate request, resulting in the provider not showing any UI. Note that this adds a new possible response: SetupNeeded, which is the server saying it doesn’t have enough information yet to authorized or reject the authentication (probably, the user needs to sign in or approve the trust root).
- class flask_openid.OpenIDResponse(resp, extensions)¶
Passed to the after_login function. Provides all the information sent from the OpenID provider. The profile information has to be requested from the server by passing a list of fields as ask_for to the
try_login()
function.- aim¶
AIM messenger address as string
- blog¶
URL of blog as string
- country¶
the country of the user as specified by ISO3166
- date_of_birth¶
date of birth as
datetime
object.
- email¶
the email address of the user
- fullname¶
the full name of the user
- gender¶
the gender of the user (
f
for femail andm
for male)
- icq¶
icq messenger number as string
- identity_url¶
the openid the user used for sign in
- image¶
URL to profile image as string
- jabber¶
jabber address as string
- language¶
the user’s preferred language as specified by ISO639
- month_of_birth¶
the month of birth of the user as integer (1 based)
- msn¶
msn name as string
- nickname¶
desired nickname of the user
- phone¶
phone number of the user as string
- postcode¶
free text that should conform to the user’s country’s postal system
- skype¶
skype name as string
- timezone¶
timezone string from the TimeZone database
- website¶
URL of website as string
- yahoo¶
yahoo messenger address as string
- year_of_birth¶
the year of birth of the user as integer
- flask_openid.COMMON_PROVIDERS¶
a dictionary of common provider name -> login URL mappings. This can be used to implement “click button to login” functionality.
Currently contains general purpose entrypoints for the following providers:
google
,yahoo
,aol
, andsteam
.