# Flask-AuthOOB
this library is Fask Authentication Out of the Box and make it fast and simple to add an authentication layer to a flask app for apis.
This library is based on [Flask-Security](https://pythonhosted.org/Flask-Security/) that already provides authentication tools in a very opiniated way. Flask-AuthOOB defines as many settings and routes as possible so that you can quickly implement authentication to a newly created flask app.
## Implementation
So the boiler plate for Flask Autentication using this library looks like
```python
from flask import Flask, request
app = Flask(__name__)
db = SQLAlchemy()
authoob = AuthOOB(app, db)
```
Flask app config must have defined the following values
```bash
APP_URL # app url where redirections are made when user validates email for exemple
API_URL # api url where lives this extensions routes
SECRET_KEY # make it possible to generate salt passwords
EMAIL_SENDER # email that appears in auth sent emails
SENDGRID_API_KEY # Sendgrid api key to send email using this provider
```
And that's all !
## Library options
it is possible to init library at the right time like this
```python
# With other extensions
authoob = AuthOOB()
# Later in code when the context is ready
authoob.init_app(app, db)
```
Then you can reach Authentication objects from authoob instance. for exemple let query users table :
```python
from app import authoob
authoob.User.query.filter_by(email='armin.ronacher@pocoo.org').count()
```
Available variables in the **authoob** instance are:
```python
authoob.User # Auth oob base user + Mixins
authoob.Role
authoob.roles_users # Relation many to many between Role and user
authoob.UserSchema # Marshmallow schema for user
authoob.RoleSchema # Marshmallow schema for roles
authoob.ma # Marshmallow instance
authoob.user_datastore # Flask Security SQLAlchemyUserDatastore instance
authoob.security # Flask Security Security instance
```
These models are almost the one given in Flask-Security implementation exemple
## Authentication endpoints
When the extention is properly loaded some default routes are defined as following:
```javascript
{
method: 'POST',
route: '/authoob/register',
payload: {"email": "register@mail.com", "password1": "1Password", "password2": "1Password"},
success_response: {token: 'AJWT token'},
fail_response: {code: '4xx', message: 'message'}
}
{
method: 'POST',
route: '/authoob/login',
payload: {"email": "register@mail.com", "password": "1Password"},
success_response: {token: 'AJWT token'},
fail_response: {code: '4xx', message: 'message'}
}
{
method: 'GET',
route: '/authoob/logout',
success_response: {token: 'AJWT token'},
fail_response: {code: '4xx', message: 'message'}
}
{
method: 'GET'
route: '/authoob/token'
headers: {"Authentication-Token": 'AJWT token'}
success_response: {token: 'AJWT token'},
fail_response: {code: '4xx', message: 'message'}
}
{
method: 'GET'
route: '/authoob/profile'
headers: {"Authentication-Token": 'AJWT token'}
success_response: 'serialized user data',
fail_response: {code: '401', message: 'message'}
}
{
method: 'GET'
route: '/authoob/profile/<user_id>'
success_response: 'serialized user data',
fail_response: {code: '4xx', message: 'message'}
}
{
method: 'PUT'
route: '/authoob/profile'
payload: {"username": "utopman", "firstname" : "eric", "lastname" : "R"] //default ones, use your own
success_response: 'serialized user data',
fail_response: {code: '4xx', message: 'message'}
}
{
method: 'PUT'
route: '/authoob/reset/password'
payload: {"password1": "newPassword", "password2": "newPassword"}
success_response: 201,
fail_response: {code: '4xx', message: 'message'}
}
{
method: 'PUT'
route: '/authoob/reset/password/token'
payload: {"password1": "newPassword", "password2": "newPassword", "token": "<token-from-ask-email>"}
success_response: 204,
fail_response: {code: '4xx', message: 'message'}
}
{
method: 'POST'
route: '/authoob/activate/<token>'
success_response: 201,
fail_response: {code: '4xx', message: 'message'},
description: 'The route to call from registration mail url'
}
```
## Add authenticated route to the rest of the application
In the rest of the api, define protected routes using Flask-Security JWT mechanism
```python
from flask_security.decorators import auth_token_required
@app.route('/my_route')
@auth_token_required
def my_route():
return jsonify({"a": "response"})
```
And from the client that consumes the API, you have to set a header with the tokens in the auth routes responses, the header to use is the one defined by Flask-Security (it is also possible to change the header name defining the key in flask configuration). The header is defined by default in the configuration with the value **SECURITY_TOKEN_AUTHENTICATION_HEADER** to `Authentication-Token`
## Other options
It is possible to change route prefix from authoob to whatever you want (and is a valid url string) by defining a custom route prefix on extention initialization
```python
authoob = AuthOOB(app, db, prefix="another_auth_prefix")
```
It is possible to extend the User model by setting a _CustomUserMixin_ property on extention instanciation
```python
class CustomUserMixin:
test_field = db.Column(db.String)
extra_updatable_fields = ["test_field"]
extra_exposed_fields = ["test_field"]
authoob = AuthOOB(app, db, CustomUserMixin=CustomUserMixin)
```
This will add the `test_field` field to the user , allows it's update and serialize it's value on `/authoob/profile` calls
## Hooks
There are available hooks in this library that make it possible to add behaviors at many points of the security layer interaction.
For exemple, let say you want to add an extra behavior on a user registeration, you will have to do the following
```python
# Define your own custom hook class
class CustomHooks:
# Add hook method on register action
def post_register(self, context):
# There is for each hook a specific context object that is a dict
# with what context looks appropriate depending on the hook
# For exemple in the post_register hook, ths context will be {"user": <New User Instance>, "payload" : request.json}
try:
role_name = "custom_user_role" if context["payload"]["type"] == 1 else "customer"
role = authoob.Role.query.filter_by(name=role_name).one()
except Exception:
abort(400)
user = context["user"]
user.roles.append(role)
db.session.add(user)
db.session.commit()
```
And you have to register your custom hooks class in flask-AuthOOB instance
```python
authoob.init_app(
app,
db,
# Note that hook is an instance
custom_hooks=CustomHooks(),
)
```
And again that is all.
There are hooks for each action (endpoint) that authoob provides with a specific context for **pre** and **post** action. So the following document describes all possibles hooks :
```yaml
- name: pre_register
context-dict:
- name: payload
content: request.json content
- name: post_register
context-dict:
- name: user
content: newly created user
- name: payload
content: request.json content
- name: mail_register
context-dict:
- name: user
content: user
- name: mail_provider
content: mail_provider
- name: pre_login
context-dict:
- name: payload
content: request.json content
- name: before_login
context-dict:
- name: payload
content: request.json content
- name: user
content: session user being logged in
- name: post_login
context-dict:
- name: user
content: session user
- name: payload
content: request.json content
- name: pre_profile
context-dict:
- name: user
content: session user
- name: post_profile
context-dict:
- name: user
content: session user
- name: response
content: dumped user profile json response
- name: pre_user_profile
context-dict:
- name: user_id
content: user id parameter
- name: post_user_profile
context-dict:
- name: user
content: user instance from user_id parameter
- name: response
content: dumped user profile json response
- name: pre_token
context-dict:
- name: user
content: session user
- name: post_token
context-dict:
- name: user
content: session user
- name: response
content: dumped token json response
- name: pre_activate
context-dict:
- name: token
content: activation token
- name: post_activate
context-dict:
- name: user
content: session user
- name: already_activated
context-dict:
- name: user
content: activated user being checked
- name: pre_reset_auth
context-dict:
- name: payload
content: request.json content
- name: post_reset_auth
context-dict:
- name: payload
content: request.json content
- name: user
content: session user
- name: pre_reset_token
context-dict:
- name: token
content: token parameter (from ask reset mail)
- name: post_reset_token
context-dict:
- name: user
content: user which password is reset
- name: token
content: token parameter (from ask reset mail)
- name: pre_update_profile
context-dict:
- name: user
content: session user
- name: payload
content: request.json content
- name: post_update_profile
context-dict:
- name: user
content: session user
- name: payload
content: request.json content
- name: response
content: dumped user profile json response
- name: pre_logout
context-dict:
- name: user
content: user being logout
- name: post_logout
context-dict: null
```
## Mail hook system
Mail default provider is based on Sendgrid api when an API key is provided on the flask config environment.
It is then possible to manage emails like this :
```python
class MailHooks:
def mail_register(self, context):
context["mail_provider"].send_mail(
to_emails=['me@home.fr', 'you@anywhere.com', context["user"].email],
subject="Email validation",
html='<h1>Super email template override</h1>'
)
return True # Returning true will make the authoob lib use this code and not trigger default email sending
```
note: Other mail hook will arrive soon.
## Configuation options
* ``PREVENT_MAIL_SEND`` : prevent send email, usefull for testing enviroments