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.
Links¶
Configuration¶
Option |
Description |
---|---|
|
The name of login endpoint. Default is
|
|
The url of login endpoint, if this is set, it
will take precedence over
|
|
The name of session to store the auth token,
if it is not set, value of
|
|
The name of the key used in session to store
user token. Default is |
API¶
- class sanic_auth.Auth(app=None)[source]¶
Authentication Manager.
- 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 byload_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.
- 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.