Sanic-Auth - Simple Authentication for Sanic

Sanic-Auth implements a minimal backend agnostic session-based user authentication mechanism for Sanic.

Quick Start

Installation

pip install --upgrade Sanic-Auth

How to use it

from sanic_auth import Auth
from sanic import Sanic, response


app = Sanic(__name__)
app.config.AUTH_LOGIN_ENDPOINT = 'login'


@app.middleware('request')
async def add_session_to_request(request):
    # setup session

auth = Auth(app)

@app.route('/login', methods=['GET', 'POST'])
async def login(request):
    message = ''
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        # fetch user from database
        user = some_datastore.get(name=username)
        if user and user.check_password(password):
            auth.login_user(request, user)
            return response.redirect('/profile')
    return response.html(HTML_LOGIN_FORM)


@app.route('/logout')
@auth.login_required
async def logout(request):
    auth.logout_user(request)
    return response.redirect('/login')


@app.route('/profile')
@auth.login_required(user_keyword='user')
async def profile(request, user):
    return response.json({'user': user})

For more details, please see documentation.

License

BSD New, see LICENSE for details.

Configuration

Option

Description

AUTH_LOGIN_ENDPOINT

The name of login endpoint. Default is "auth.login"

AUTH_LOGIN_URL

The url of login endpoint, if this is set, it will take precedence over AUTH_LOGIN_ENDPOINT. Default is None, which means AUTH_LOGIN_ENDPOINT will be used.

AUTH_SESSION_NAME

The name of session to store the auth token, if it is not set, value of SESSION_NAME will be used, if that is not set either, the default key name "session" will be used.

AUTH_TOKEN_NAME

The name of the key used in session to store user token. Default is "_auth"

API

class sanic_auth.Auth(app=None)[source]

Authentication Manager.

current_user(request)[source]

Get the current logged in user.

Return None if no user logged in.

get_session(request)[source]

Get the session object associated with current request

handle_no_auth(request)[source]

Handle unauthorized user

load_user(token)[source]

Load user with token.

Return a User object, the default implementation use a proxy object of User, Sanic-Auth can be remain backend agnostic this way.

Override this with routine that loads user from database if needed.

login_required(route=None, *, user_keyword=None, handle_no_auth=None)[source]

Decorator to make routes only accessible with authenticated user.

Redirect visitors to login view if no user logged in.

Parameters:
  • route – the route handler to be protected

  • user_keyword – keyword only arugment, if it is not None, and set to a string representing a valid python identifier, a user object loaded by load_user() will be injected into the route handler’s arguments. This is to save from loading the user twice if the current user object is going to be used inside the route handler.

  • handle_no_auth – keyword only arugment, if it is not None, and set to a function this will be used to handle an unauthorized request.

login_user(request, user)[source]

Log in a user.

The user object will be serialized with Auth.serialize() and the result, usually a token representing the logged in user, will be placed into the request session.

logout_user(request)[source]

Log out any logged in user in this session.

Return the user token or None if no user logged in.

no_auth_handler(handle_no_auth)[source]

Decorator to handle an unauthorized request

serialize(user)[source]

Serialize the user, returns a token to be placed into session

serializer(user_serializer)[source]

Decorator to set a custom user serializer

setup(app)[source]

Setup with application’s configuration.

This method be called automatically if the application is provided upon initialization

user_loader(load_user)[source]

Decorator to set a custom user loader that loads user with token

class sanic_auth.User(id, name)

A User proxy type, used by default implementation of Auth.load_user()

id

Alias for field number 0

name

Alias for field number 1

Full Example

from datetime import datetime
from sanic import Sanic, response

from sanic_auth import Auth, User


app = Sanic(__name__)
app.config.AUTH_LOGIN_ENDPOINT = 'login'
auth = Auth(app)

# NOTE
# For demonstration purpose, we use a mock-up globally-shared session object.
session = {}
@app.middleware('request')
async def add_session(request):
    request.ctx.session = session



LOGIN_FORM = '''
<h2>Please sign in, you can try:</h2>
<dl>
<dt>Username</dt> <dd>demo</dd>
<dt>Password</dt> <dd>1234</dd>
</dl>
<p>{}</p>
<form action="" method="POST">
  <input class="username" id="name" name="username"
    placeholder="username" type="text" value=""><br>
  <input class="password" id="password" name="password"
    placeholder="password" type="password" value=""><br>
  <input id="submit" name="submit" type="submit" value="Sign In">
</form>
'''


@app.route('/login', methods=['GET', 'POST'])
async def login(request):
    message = ''
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        # for demonstration purpose only, you should use more robust method
        if username == 'demo' and password == '1234':
            # use User proxy in sanic_auth, this should be some ORM model
            # object in production, the default implementation of
            # auth.login_user expects User.id and User.name available
            user = User(id=1, name=username)
            auth.login_user(request, user)
            return response.redirect('/')
        message = 'invalid username or password'
    return response.html(LOGIN_FORM.format(message))


@app.route('/logout')
@auth.login_required
async def logout(request):
    auth.logout_user(request)
    return response.redirect('/login')


@app.route('/')
@auth.login_required(user_keyword='user')
async def profile(request, user):
    content = '<a href="/logout">Logout</a><p>Welcome, %s</p>' % user.name
    return response.html(content)


def handle_no_auth(request):
    return response.json(dict(message='unauthorized'), status=401)


@app.route('/api/user')
@auth.login_required(user_keyword='user', handle_no_auth=handle_no_auth)
async def api_profile(request, user):
    return response.json(dict(id=user.id, name=user.name))


if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8000, debug=True)

Changelog

  • 0.3.0

    • Support Sanic 20.3.0

    • Drop python 3.5 support.

  • 0.2.0

    • Handling of unauthorized request can be customized.

    • Properly handle async user loader.

  • 0.1.0

    First public release.

Indices and tables