Flask extension for using pyoidc as authentication for Flask apps.
Clone or download
zamzterz Allow the user session lifetime to be configured via Flask. (#35)
Don't use the ID Token expiration time since that is unrelated to the
user sessions kept by the client.
Latest commit e410055 Oct 6, 2018

README.md

Flask-pyoidc

PyPI codecov.io Build Status

This Flask extension provides simple OpenID Connect authentication, backed by pyoidc.

Currently only "Authorization Code Flow" is supported.

Example

Have a look at the example Flask app for a full example of how to use this extension.

Configuration

Provider and client configuration

Both static and dynamic provider configuration discovery, as well as static and dynamic client registration, is supported. The different modes of provider configuration can be combined with any of the client registration modes.

Dynamic provider configuration

To use a provider which supports dynamic discovery it suffices to specify the issuer URL:

from flask_pyoidc.provider_configuration import ProviderConfiguration

config = ProviderConfiguration(issuer='https://op.example.com', [client configuration])

Static provider configuration

To use a provider not supporting dynamic discovery, the static provider metadata can be specified:

from flask_pyoidc.provider_configuration import ProviderConfiguration, ProviderMetadata

provider_metadata = ProviderMetadata(issuer='https://op.example.com', 
                                     authorization_endpoint='https://op.example.com/auth',
                                     jwks_uri='https://op.example.com/jwks')
config = ProviderConfiguration(provider_metadata=provider_metadata, [client configuration])

See the OpenID Connect specification for more information about the provider metadata.

Static client registration

If you have already registered a client with the provider, specify the client credentials directly:

from flask_pyoidc.provider_configuration import ProviderConfiguration, ClientMetadata

client_metadata = ClientMetadata(client_id='cl41ekfb9j', client_secret='m1C659wLipXfUUR50jlZ')
config = ProviderConfiguration([provider configuration], client_metadata=client_metadata)

Note: The redirect URIs registered with the provider MUST include <application_url>/redirect_uri, where <application_url> is the URL of the Flask application.

Dynamic client registration

To dynamically register a new client for your application, the required client registration info can be specified:

from flask_pyoidc.provider_configuration import ProviderConfiguration, ClientRegistrationInfo

client_registration_info = ClientRegistrationInfo(client_name='Test App', contacts=['dev@rp.example.com'])
config = ProviderConfiguration([provider configuration], client_registration_info=client_registration_info)

Flask configuration

The application using this extension MUST set the following builtin configuration values of Flask:

  • SERVER_NAME: MUST be the same as <flask_url> if using static client registration.
  • SECRET_KEY: This extension relies on Flask sessions, which requires SECRET_KEY.

You may also configure the way the user sessions created by this extension are handled:

  • OIDC_SESSION_PERMANENT: If set to True (which is the default) the user session will be kept until the configured session lifetime (see below). If set to False the session will be deleted when the user closes the browser.
  • PERMANENT_SESSION_LIFETIME: Control how long a user session is valid, see Flask documentation for more information.

Session refresh

If your provider supports the prompt=none authentication request parameter, this extension can automatically refresh user sessions. This ensures that the user attributes (OIDC claims, user being active, etc.) are kept up-to-date without having to log the user out and back in. To enable and configure the feature, specify the interval (in seconds) between refreshes:

from flask_pyoidc.provider_configuration import ProviderConfiguration

config = ProviderConfiguration(session_refresh_interval_seconds=1800, [provider/client config]

Note: The user will still be logged out when the session expires (as described above).

Protect an endpoint by authentication

To add authentication to one of your endpoints use the oidc_auth decorator:

import flask
from flask import Flask, jsonify

from flask_pyoidc.provider_configuration import ProviderConfiguration
from flask_pyoidc.user_session import UserSession
from flask_pyoidc.flask_pyoidc import OIDCAuthentication

app = Flask(__name__)
config = ProviderConfiguration(...)
auth = OIDCAuthentication({'default': config}, app)

@app.route('/login')
@auth.oidc_auth('default')
def index():
    user_session = UserSession(flask.session)
    return jsonify(access_token=user_session.access_token,
                   id_token=user_session.id_token,
                   userinfo=user_session.userinfo)

After a successful login, this extension will place three things in the user session (if they are received from the provider):

Using multiple providers

To allow users to login with multiple different providers, configure all of them in the OIDCAuthentication constructor and specify which one to use by name for each endpoint:

from flask_pyoidc.provider_configuration import ProviderConfiguration
from flask_pyoidc.flask_pyoidc import OIDCAuthentication

app = Flask(__name__)
auth = OIDCAuthentication({'provider1': ProviderConfiguration(...), 'provider2': ProviderConfiguration(...)}, app)

@app.route('/login1')
@auth.oidc_auth('provider1')
def login1():
    pass

@app.route('/login2')
@auth.oidc_auth('provider2')
def login2():
    pass

User logout

To support user logout, use the oidc_logout decorator:

@app.route('/logout')
@auth.oidc_logout
def logout():
    return 'You\'ve been successfully logged out!'

This extension also supports RP-Initiated Logout, if the provider allows it. Make sure the end_session_endpoint is defined in the provider metadata to enable notifying the provider when the user logs out.

Specify the error view

If an OAuth error response is received, either in the authentication or token response, it will be passed to the "error view", specified using the error_view decorator:

from flask import jsonify

@auth.error_view
def error(error=None, error_description=None):
 return jsonify({'error': error, 'message': error_description})

The function specified as the error view MUST accept two parameters, error and error_description, which corresponds to the OIDC error parameters, and return the content that should be displayed to the user.

If no error view is specified, a generic error message will be displayed to the user.