Flask-OpenID — Flask-OpenID (original) (raw)
Flask-OpenID is an extension to Flask that allows you to add OpenIDbased 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 theOPENID_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 theinit_app() method.
The list of URL roots that are safe to redirect the user to are passed viasafe_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 abefore_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 asloginhandler():
@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 %}
Sign in
{% if error %}Error: {{ error }}
{% endif %}OpenID:
{% 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 %}
Create Profile
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:
- Name:
- E-Mail:
If you don't want to proceed, you can sign out 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 theOPENID_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 that fetch_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 andask_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 andask_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 = None¶
AIM messenger address as string
blog = None¶
URL of blog as string
country = None¶
the country of the user as specified by ISO3166
date_of_birth = None¶
date of birth as datetime object.
email = None¶
the email address of the user
extensions = None¶
Hash of the response object from the OpenID Extension by the
fullname = None¶
the full name of the user
gender = None¶
the gender of the user (f for femail and m for male)
icq = None¶
icq messenger number as string
identity_url = None¶
the openid the user used for sign in
image = None¶
URL to profile image as string
jabber = None¶
jabber address as string
language = None¶
the user’s preferred language as specified by ISO639
month_of_birth = None¶
the month of birth of the user as integer (1 based)
msn = None¶
msn name as string
nickname = None¶
desired nickname of the user
phone = None¶
phone number of the user as string
postcode = None¶
free text that should conform to the user’s country’s postal system
skype = None¶
skype name as string
timezone = None¶
timezone string from the TimeZone database
website = None¶
URL of website as string
yahoo = None¶
yahoo messenger address as string
year_of_birth = None¶
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, and steam.