RSS

python-cas Flask Example

An example project to demo how to integrate python-cas into a Flask app.

python-cas is Python CAS (Central Authentication Server) client library support CAS 1.0/2.0/3.0. It was initially split from django-cas-ng to support any Python app to easily implement a CAS client.

This post is an example project to demo how to integrate python-cas into a Flask app. This should give you idea how to integrate python-cas into any other Python app.

If you are new to CAS, to get started about CAS, please checkout CAS 101.

Flask Example Project

I created a Flask project and integrated python-cas to support CAS login/logout, The integration is pretty straightforward, the key point is Flask need call some CAS API list below:

Design

/login

/login to handle client login request and redirect to next after login successfully.

Accept query parameters:

  • next: string. The redirect URL after login successfully. e.g. %2Fprofile. NOTE: Need URL encoded.
  • ticket: string. CAS service ticket. When CAS authenticated success, it will have this parameter in callback. e.g. ST-1580754576-qbgmySJHjm.

After login to CAS successfully, set create a session to local Flask app to save login state. The session var is username.

/logout and /logout_callback

/logout will send logout request to CAS server to logout from CAS server, after CAS logged out user, CAS will redirect to /logout_callback, it will logged user out from Flask app locally by delete session.

/profile

/profile show logged user info. e.g. username.

Sequence Diagram

CAS login / logout flow sequence diagram as below:

CAS Login / Logout Flow

Code

The code is pretty simple, less than 50 lines of code. All the hard work is done by python-cas.

$ cloc app.py
--------------------------------------------------------------
Language        files        blank      comment           code
--------------------------------------------------------------
Python              1           18            0             47
--------------------------------------------------------------

The source code as below, it is self explained:

from flask import Flask, request, session, redirect, url_for
from cas import CASClient

app = Flask(__name__)
app.secret_key = 'V7nlCN90LPHOTA9PGGyf'

cas_client = CASClient(
    version=3,
    service_url='http://localhost:5000/login?next=%2Fprofile',
    server_url='https://django-cas-ng-demo-server.herokuapp.com/cas/'
)


@app.route('/')
def index():
    return redirect(url_for('login'))


@app.route('/profile')
def profile(method=['GET']):
    if 'username' in session:
        return 'Logged in as %s. <a href="/logout">Logout</a>' % session['username']
    return 'Login required. <a href="/login">Login</a>', 403


@app.route('/login')
def login():
    if 'username' in session:
        # Already logged in
        return redirect(url_for('profile'))

    next = request.args.get('next')
    ticket = request.args.get('ticket')
    if not ticket:
        # No ticket, the request come from end user, send to CAS login
        cas_login_url = cas_client.get_login_url()
        app.logger.debug('CAS login URL: %s', cas_login_url)
        return redirect(cas_login_url)

    # There is a ticket, the request come from CAS as callback.
    # need call `verify_ticket()` to validate ticket and get user profile.
    app.logger.debug('ticket: %s', ticket)
    app.logger.debug('next: %s', next)

    user, attributes, pgtiou = cas_client.verify_ticket(ticket)

    app.logger.debug(
        'CAS verify ticket response: user: %s, attributes: %s, pgtiou: %s', user, attributes, pgtiou)

    if not user:
        return 'Failed to verify ticket. <a href="/login">Login</a>'
    else:  # Login successfully, redirect according `next` query parameter.
        session['username'] = user
        return redirect(next)


@app.route('/logout')
def logout():
    redirect_url = url_for('logout_callback', _external=True)
    cas_logout_url = cas_client.get_logout_url(redirect_url)
    app.logger.debug('CAS logout URL: %s', cas_logout_url)

    return redirect(cas_logout_url)


@app.route('/logout_callback')
def logout_callback():
    # redirect from CAS logout request after CAS logout successfully
    session.pop('username', None)
    return 'Logged out from CAS. <a href="/login">Login</a>'

You can clone it from https://github.com/python-cas/flask-example/

Run your local server:

$ git clone [email protected]:python-cas/flask-example.git python-cas-flask-example
$ cd python-cas-flask-example
$ pip install -r requirements.txt
$ sh run_debug_server.sh
 * Serving Flask app "app.py" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

You may curious the actual CAS flow, Here is the debug for reference, you can get more insight about how CAS redirect and ticket validation.

# Login
"GET /login HTTP/1.1" 302 -
DEBUG in app: CAS login URL: https://django-cas-ng-demo-server.herokuapp.com/cas/login?service=http%3A%2F%2Flocalhost%3A5000%2Flogin%3Fnext%3D%252Fprofile
"GET /login HTTP/1.1" 302 -
DEBUG in app: ticket: ST-1580754576-qbgmySJHjmvdeFjy4ZGwkA1IwxGcZxL4
DEBUG in app: next: /profile
DEBUG in app: user: admin, attributes: {}, pgtiou: None
"GET /login?next=%2Fprofile&ticket=ST-1580754576-qbgmySJHjmvdeFjy4ZGwkA1IwxGcZxL4 HTTP/1.1" 302 -
"GET /profile HTTP/1.1" 200 -

# Logout
DEBUG in app: CAS logout URL: https://django-cas-ng-demo-server.herokuapp.com/cas/logout?service=http%3A%2F%2Flocalhost%3A5000%2Flogout_callback
"GET /logout HTTP/1.1" 302 -
"GET /logout_callback HTTP/1.1" 200 -

Live Demo

If you want to see a live demo, you can click here. The following username/password can be used to login in demo server.

username: admin
password: django-cas-ng

Reference