Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
Rebecka Gulliksson committed Feb 11, 2016
0 parents commit e1e2d10
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[bumpversion]
current_version = 0.0.1
commit = True
tag = True

[bumpversion:file:setup.py]

5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.pyc
*.egg-info
build/
dist/
.idea/
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Flask-pyoidc

![PyPI](https://img.shields.io/pypi/v/flask-pyoidc.svg)

This repository contains an example of how to use the [pyoidc](https://github.com/rohe/pyoidc)
library to provide simple OpenID Connect authentication (using the ["Code Flow"](http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth).

## Usage

The extension support both static and dynamic provider configuration discovery as well as static
and dynamic client registration. The different modes of provider configuration can be combined in
any way with the different client registration modes.

* Static provider configuration: `OIDCAuthentication(provider_configuration_info=provider_config)`,
where `provider_config` is a dictionary containing the [provider metadata](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata).
* Dynamic provider configuration: `OIDCAuthentication(issuer=issuer_url)`, where `issuer_url`
is the issuer URL of the provider.
* Static client registration: `OIDCAuthentication(client_registration_info=client_info)`, where
`client_info` is all the [registered metadata](https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse)
about the client. The `redirect_uris` registered with the provider MUST include
`<flask_url>/redirect_uri`, where `<flask_url>` is the URL for the Flask application.



The application using this extension MUST set the following [builtin configuration values of Flask](http://flask.pocoo.org/docs/0.10/config/#builtin-configuration-values):

* `SERVER_NAME` (MUST be the same as `<flask_url>` if using static client registration
* `SECRET_KEY` (this extension relies on Flask session, which requires `SECRET_KEY`)

Have a look at the example Flask app in [app.py](example/app.py) for an idea of how to use it.
22 changes: 22 additions & 0 deletions example/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import flask
from flask import Flask, jsonify

from flask_pyoidc import OIDCAuthentication

PORT = 5000
app = Flask(__name__)

app.config.update({'SERVER_NAME': 'localhost:{}'.format(PORT),
'SECRET_KEY': 'dev_key'})
auth = OIDCAuthentication(app, issuer="https://localhost:50009")


@app.route('/')
@auth.oidc_auth
def index():
return jsonify(id_token=flask.g.id_token.to_dict(), access_token=flask.g.access_token,
userinfo=flask.g.userinfo.to_dict())


if __name__ == '__main__':
app.run(port=PORT)
17 changes: 17 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from setuptools import setup, find_packages

setup(
name='Flask-pyoidc',
version='0.0.1',
packages=find_packages('src'),
package_dir={'': 'src'},
url='https://github.com/its-dirg/flask-pyoidc',
license='Apache 2.0',
author='Rebecka Gulliksson',
author_email='[email protected]',
description='Flask extension for OpenID Connect authentication.',
install_requires=[
'oic',
'Flask'
]
)
106 changes: 106 additions & 0 deletions src/flask_pyoidc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import functools

import flask
from flask.helpers import url_for
from oic.oauth2 import rndstr
from oic.oic import Client
from oic.oic.message import ProviderConfigurationResponse, RegistrationRequest, \
AuthorizationResponse
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
from werkzeug.utils import redirect


class OIDCAuthentication(object):
def __init__(self, flask_app, client_registration_info=None, issuer=None,
provider_configuration_info=None):
self.app = flask_app

self.client = Client(client_authn_method=CLIENT_AUTHN_METHOD)
if not issuer and not provider_configuration_info:
raise ValueError(
'Either \'issuer\' (for dynamic discovery) or \'provider_configuration_info\' (for static configuration must be specified.')
if issuer and not provider_configuration_info:
self.client.provider_config(issuer)
else:
self.client.handle_provider_config(
ProviderConfigurationResponse(**provider_configuration_info),
provider_configuration_info['issuer'])

self.client_registration_info = client_registration_info or {}
if client_registration_info and 'client_id' in client_registration_info:
# static client info provided
self.client.store_registration_info(RegistrationRequest(**client_registration_info))
else:
# do dynamic registration
self.app.add_url_rule('/redirect_uri', 'redirect_uri',
self._handle_authentication_response)
with self.app.app_context():
self.client_registration_info['redirect_uris'] = url_for('redirect_uri')
self.client.register(self.client.provider_info['registration_endpoint'],
**self.client_registration_info)

self.callback = None

def _authenticate(self):
if flask.g.get('userinfo', None):
return self.callback()

flask.session['state'] = rndstr()
flask.session['nonce'] = rndstr()
args = {
'client_id': self.client.client_id,
'response_type': 'code',
'scope': ['openid'],
'redirect_uri': self.client.registration_response['redirect_uris'][0],
'state': flask.session['state'],
'nonce': flask.session['nonce'],
}

auth_req = self.client.construct_AuthorizationRequest(request_args=args)
login_url = auth_req.request(self.client.authorization_endpoint)
return redirect(login_url)

def _handle_authentication_response(self):
# parse authentication response
query_string = flask.request.query_string.decode('utf-8')
authn_resp = self.client.parse_response(AuthorizationResponse, info=query_string,
sformat='urlencoded')

if authn_resp['state'] != flask.session['state']:
raise ValueError('The \'state\' parameter does not match.')

# do token request
args = {
'code': authn_resp['code'],
'redirect_uri': self.client.registration_response['redirect_uris'][0],
'client_id': self.client.client_id,
'client_secret': self.client.client_secret
}
token_resp = self.client.do_access_token_request(scope='openid', state=authn_resp['state'],
request_args=args,
authn_method='client_secret_basic')
id_token = token_resp['id_token']
if id_token['nonce'] != flask.session['nonce']:
raise ValueError('The \'nonce\' parameter does not match.')
access_token = token_resp['access_token']

# do userinfo request
userinfo = self.client.do_user_info_request(state=authn_resp['state'])
if userinfo['sub'] != id_token['sub']:
raise ValueError('The \'sub\' of userinfo does not match \'sub\' of ID Token.')

# store the current user
flask.g.id_token = id_token
flask.g.access_token = access_token
flask.g.userinfo = userinfo

return self.callback()

def oidc_auth(self, f):
self.callback = f

@functools.wraps(f)
def wrapper():
return self._authenticate()

return wrapper

0 comments on commit e1e2d10

Please sign in to comment.