Examples

The complete, runnable apps live in the repository’s examples/ directory. The test suite smoke-tests these examples so documentation snippets do not have to be maintained separately.

Single-file application

Use this pattern for a small app with a module-level Flask application. GitHub(app) initializes the extension immediately:

"""The smallest app: one file, module-level ``app``.

Run it with::

    export GITHUB_CLIENT_ID=... GITHUB_CLIENT_SECRET=... SECRET_KEY=...
    flask --app examples/single_file run
"""

import os

import flask

import flask_githubkit

app = flask.Flask(__name__)
app.config.update(
    SECRET_KEY=os.environ["SECRET_KEY"],
    GITHUB_CLIENT_ID=os.environ["GITHUB_CLIENT_ID"],
    GITHUB_CLIENT_SECRET=os.environ["GITHUB_CLIENT_SECRET"],
    GITHUB_REDIRECT_URI="http://localhost:5000/callback",
)
github = flask_githubkit.GitHub(app)


@app.route("/")
def index():
    return '<a href="/login">Sign in with GitHub</a>'


@app.route("/login")
@github.login
def login():
    """Replaced by a redirect to GitHub."""


@app.route("/callback")
@github.callback
def callback(access_token):
    if access_token is None:
        return "Access was denied.", 403
    return flask.redirect("/")

Run it from the repository root after setting SECRET_KEY, GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET:

flask --app examples/single_file run

Application factory

An application factory creates the Flask app inside a function. This pattern is useful for tests, multiple app configurations, and larger projects. Create the extension once, then call init_app(app) inside the factory:

"""App factory: create the extension once, attach it in ``create_app``.

Run it with::

    export GITHUB_CLIENT_ID=... GITHUB_CLIENT_SECRET=... SECRET_KEY=...
    flask --app "examples/factory:create_app" run
"""

import os

import flask

import flask_githubkit

# Normally this lives in extensions.py so any module can import it.
github = flask_githubkit.GitHub()


def create_app() -> flask.Flask:
    app = flask.Flask(__name__)
    app.config.update(
        SECRET_KEY=os.environ["SECRET_KEY"],
        GITHUB_CLIENT_ID=os.environ["GITHUB_CLIENT_ID"],
        GITHUB_CLIENT_SECRET=os.environ["GITHUB_CLIENT_SECRET"],
        GITHUB_REDIRECT_URI="http://localhost:5000/callback",
        # Optional. Both can also be passed to GitHub(...).
        GITHUB_SCOPE="read:user user:email",
        GITHUB_LOGIN_VIEW="login",
    )
    github.init_app(app)

    @app.route("/login")
    @github.login
    def login():
        """Replaced by a redirect to GitHub."""

    @app.route("/callback")
    @github.callback
    def callback(access_token):
        if access_token is None:
            return "Access was denied.", 403
        return flask.redirect("/")

    @app.route("/")
    def index():
        return '<a href="/login">Sign in with GitHub</a>'

    return app

Blueprint

Blueprints give routes an endpoint prefix. In the following example, the login endpoint is auth.login. The extension discovers this from @github.login, so no GITHUB_LOGIN_VIEW setting is needed:

"""Blueprint: login and callback live under ``/auth``.

``@github.login`` marks the view, so after an expired refresh token the
user is sent to ``auth.login`` without any ``login_view`` setting. The
callback URL registered in your OAuth App must be
``http://localhost:5000/auth/callback``.

Run it with::

    export GITHUB_CLIENT_ID=... GITHUB_CLIENT_SECRET=... SECRET_KEY=...
    flask --app "examples/blueprint:create_app" run
"""

import os

import flask

import flask_githubkit

github = flask_githubkit.GitHub()

auth = flask.Blueprint("auth", __name__, url_prefix="/auth")


@auth.route("/login")
@github.login
def login():
    """Replaced by a redirect to GitHub."""


@auth.route("/callback")
@github.callback
def callback(access_token):
    if access_token is None:
        return "Access was denied.", 403
    return flask.redirect("/")


@auth.route("/logout")
def logout():
    github.logout()
    return flask.redirect("/")


def create_app() -> flask.Flask:
    app = flask.Flask(__name__)
    app.config.update(
        SECRET_KEY=os.environ["SECRET_KEY"],
        GITHUB_CLIENT_ID=os.environ["GITHUB_CLIENT_ID"],
        GITHUB_CLIENT_SECRET=os.environ["GITHUB_CLIENT_SECRET"],
        GITHUB_REDIRECT_URI="http://localhost:5000/auth/callback",
    )
    github.init_app(app)
    app.register_blueprint(auth)
    return app

The callback URL registered with GitHub must include the Blueprint prefix, for example http://localhost:5000/auth/callback.

Call the API from a route

github.client() returns a GitHubKit client for the signed-in user, or None when no token is available. Use the client’s typed REST methods:

"""Calling the GitHub API from a route with ``github.client()``.

``client()`` returns a GitHubKit client for the signed-in user, or
``None`` when nobody is signed in. Expired access tokens are refreshed
for you. If the refresh token has also expired, the user is sent to
the login view.

Run it with::

    export GITHUB_CLIENT_ID=... GITHUB_CLIENT_SECRET=... SECRET_KEY=...
    flask --app "examples/routes:create_app" run
"""

import collections.abc
import functools
import os
import typing

import flask

import flask_githubkit

github = flask_githubkit.GitHub()


def login_required(
    view: collections.abc.Callable[..., typing.Any],
) -> collections.abc.Callable[..., typing.Any]:
    @functools.wraps(view)
    def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
        if github.client() is None:
            return flask.redirect(flask.url_for("login"))
        return view(*args, **kwargs)

    return wrapper


def create_app() -> flask.Flask:
    app = flask.Flask(__name__)
    app.config.update(
        SECRET_KEY=os.environ["SECRET_KEY"],
        GITHUB_CLIENT_ID=os.environ["GITHUB_CLIENT_ID"],
        GITHUB_CLIENT_SECRET=os.environ["GITHUB_CLIENT_SECRET"],
        GITHUB_REDIRECT_URI="http://localhost:5000/callback",
    )
    github.init_app(app)

    @app.route("/login")
    def login():
        # The call form, for when you want to run code before redirecting.
        return github.login()

    @app.route("/callback")
    @github.callback
    def callback(access_token):
        if access_token is None:
            return "Access was denied.", 403
        return flask.redirect(flask.url_for("profile"))

    @app.route("/profile")
    @login_required
    def profile():
        user = github.client().rest.users.get_authenticated().parsed_data
        return flask.jsonify(login=user.login, name=user.name)

    @app.route("/logout")
    def logout():
        github.logout()
        return flask.redirect(flask.url_for("login"))

    return app

Prefer API calls over reading the access token directly. If server-side integration code needs the token, it is available as client.auth.token. It is a bearer credential: do not return it to the browser, include it in a URL, or write it to logs. An API request may refresh the token, so read the value after using the client if you need the refreshed credential.