Skip to main content

Build status Code coverage Maintenance yes GitHub license Documentation Status

Flask-Authorize

Flask-Authorize is a Flask extension designed to simplify the process of incorporating Access Control Lists (ACLs) and Role-Based Access Control (RBAC) into applications housing sensitive data, allowing developers to focus on the actual code for their application instead of logic for enforcing permissions. It uses a unix-like permissions scheme for enforcing access permissions on existing content, and also provides mechanisms for globally enforcing permissions throughout an application.

Installation

To install the latest stable release via pip, run:

$ pip install Flask-Authorize

Or via uv, run:

$ uv add Flask-Authorize

To install the bleeding-edge version of the project (not recommended):

$ git clone http://github.com/bprinty/Flask-Authorize.git
$ cd Flask-Authorize
$ uv sync --all-groups

Usage

Below details a minimal example showcasing how to use the extension. First, to set up the flask application with extensions:

from flask import Flask
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from flask_authorize import Authorize

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
login = LoginManager(app)
authorize = Authorize(app)

Defining database models:

from flask_authorize import RestrictionsMixin, AllowancesMixin
from flask_authorize import PermissionsMixin


# mapping tables
UserGroup = db.Table(
    'user_group', db.Model.metadata,
    db.Column('user_id', db.Integer, db.ForeignKey('users.id')),
    db.Column('group_id', db.Integer, db.ForeignKey('groups.id'))
)


UserRole = db.Table(
    'user_role', db.Model.metadata,
    db.Column('user_id', db.Integer, db.ForeignKey('users.id')),
    db.Column('role_id', db.Integer, db.ForeignKey('roles.id'))
)


# models
class User(db.Model):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False, unique=True)

    # `roles` and `groups` are reserved words that *must* be defined
    # on the `User` model to use group- or role-based authorization.
    roles = db.relationship('Role', secondary=UserRole)
    groups = db.relationship('Group', secondary=UserGroup)


class Group(db.Model, RestrictionsMixin):
    __tablename__ = 'groups'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False, unique=True)


class Role(db.Model, AllowancesMixin):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False, unique=True)


class Article(db.Model, PermissionsMixin):
    __tablename__ = 'articles'
    __permissions__ = dict(
        owner=['read', 'update', 'delete', 'revoke'],
        group=['read', 'update'],
        other=['read']
    )

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), index=True, nullable=False)

Defining endpoint actions:

from flask import jsonify
from werkzeug import NotFound, Unauthorized

@app.route('/articles', methods=['POST'])
@login.logged_in
@authorize.create(Article)
def article():
    article = Article(**request.json)
    db.session.add(article)
    db.session.commit()
    return jsonify(msg='Created Article'), 200

@app.route('/articles/<int:ident>', methods=['GET', 'PUT', 'DELETE'])
@login.logged_in
def single_article(ident):
    article = db.session.query(Article).filter_by(id=ident).first()
    if not article:
        raise NotFound

    if request.method == 'GET':

        # check if the current user is authorized to read the article
        if not authorize.read(article):
            raise Unauthorized

        return jsonify(id=article.id, name=article.name), 200

    elif request.method == 'PUT':

        # check if the current user is authorized to update to the article
        if not authorize.update(article):
            raise Unauthorized

        for key, value in request.json.items():
            setattr(article, key, value)
        db.session.commit()

        return jsonify(id=article.id, name=article.name), 200

    elif request.method == 'DELETE':

        # check if the current user is associated with the 'admin' role
        if not authorize.delete(article) or \
           not authorize.has_role('admin'):
            raise Unauthorized

        db.session.delete(article)
        db.session.commit()

    return

@app.route('/articles/<int:ident>/revoke', methods=['POST'])
@login.logged_in
def revoke_article(ident):
    article = db.session.query(Article).filter_by(id=ident).first()
    if not article:
        raise NotFound

    # check if the current user can revoke the article
    if not authorize.revoke(article):
        raise Unauthorized

    article.revoked = True
    db.session.commit()

    return

Additionally, if you've configured your application to dispatch request processing to API functions, you can use the authorize extension object as a decorator:

@authorize.create(Article)
def create_article(name):
    article = Article(**request.json)
    db.session.add(article)
    db.session.commit()
    return article

@authorize.read
def read_article(article):
    return article

@authorize.update
def update_article(article, **kwargs):
    for key, value in request.json.items():
        setattr(article, key, value)
        db.session.commit()
    return article

@authorize.delete
def delete_article(article):
    db.session.delete(article)
    return

@authorize.revoke
def revoke_article(article):
    article.revoke = True
    db.session.commit()
    return

@authorize.has_role('admin')
def get_admin_articles():
    pass

Using the extension as a decorator goes a long way in removing boilerplate associated with permissions checking. Additionally, using the authorize extension object as a decorator will implicitly check the current user's access to each argument or keyword argument to the function. For example, if your method takes two Article objects and merges them into one, you can add permissions for both operations like so:

@authorize.read
@authorize.create(Article)
def merge_articles(article1, article2):
    new_article = Article(name=article1.name + article.2.name)
    db.session.add(new_article)
    db.session.delete(article1, article2)
    db.session.commit()
    return new_article

This function will ensure that the current user has read access to both articles and also create permissions on the Article model itself. If the authorization criteria aren't satisfied, an Unauthorized error will be thrown.

Finally, the authorize operator is also available in Jinja templates:

<!-- button for creating new article -->
{% if authorize.create('articles') %}
    <button>Create Article</button>
{% endif %}

<!-- display article feed -->
{% for article in articles %}

    <!-- show article if user has read access -->
    {% if authorize.read(article) %}
        <h1>{{ article.name }}</h1>

        <!-- add edit button for users who can update-->
        {% if authorize.update(article) %}
            <button>Update Article</button>
        {% endif %}

        <!-- add delete button for administrators -->
        {% if authorize.in_group('admins') %}
            <button>Delete Article</button>
        {% endif %}

    {% endif %}
{% endfor %}

Documentation

For more detailed documentation, see the Docs.

Questions/Feedback

File an issue in the GitHub issue tracker.

Metadata

Release files for Flask-Authorize 0.2.8

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for Flask-Authorize 0.2.8
File Size Uploaded
flask_authorize-0.2.8.tar.gz 364.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for Flask-Authorize 0.2.8
File Interpreter ABI Platform
flask_authorize-0.2.8-py3-none-any.whl Python 3 none any Details

Total release size: 377.6 kB

Release files / flask_authorize-0.2.8.tar.gz

Download URL flask_authorize-0.2.8.tar.gz
Size 364.8 kB
Tags Source
SHA-256 checksum
How to use checksums
ad426f0860cd678c2cf105eee7f1af41f33d42ff26236b1f4baaf48415ea7f70
BLAKE2b-256 checksum
How to use checksums
ca3cfcdebd5d30b5495080acde6e8e6259e941862b9a1a1e5232666ca0c5d320
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.29 {"installer":{"name":"uv","version":"0.9.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / flask_authorize-0.2.8-py3-none-any.whl

Download URL flask_authorize-0.2.8-py3-none-any.whl
Size 12.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f95fcf69c31010d22c89803b5d6ae0010f2713ea73b3edb04a937d0a80830fd2
BLAKE2b-256 checksum
How to use checksums
40d0872eff6e69b5eab1aaa2d88d71c439be05d7405c7d1e2bf7a48fbd0a5a4e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.29 {"installer":{"name":"uv","version":"0.9.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.2.8 This release

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page