Customs - Authentication made easy¶
Passport.js inspired library for setting up server authentication in Python. Customs creates a protective layer around Flask APIs with minimal configuration and allows users to configure and use multiple authentication strategies with ease.
Concept¶
Customs consists of a single customs object that can use strategies to protect API endpoints, or create a safe_zone around a set of endpoints.
Batteries included¶
Customs comes out of the box with the following strategies:
Local
Basic
JWT
Google
Github
Facebook
The list is growing, but if you still cannot find what you’re looking for it is very easy to create a specific strategy for your purpose.
Quickstart¶
Getting started with Customs is easy, just install Customs, configure an authentication strategy and start protecting your endpoints. In this quickstart guid we’ll set up basic authentication to protect a specific route. Check out the rest of the docs and examples to see all possibilities.
1. Install Customs
Install the package using pip
:
$ pip install customs
2. Configure a strategy
Pick a base strategy to work from, in this case the BasicStrategy
. This strategy will automatically check the Authorization
header in the request and look for base64 encoded credentials. All we got to do here is tell the strategy how to check the username and password and how to get user information from a username.
class BasicAuthentication(BasicStrategy):
def validate_credentials(self, username: str, password: str) -> Dict:
""" Method to validate credentials (username and password).
"""
# If the user is in the database and the password is correct
if username in DATABASE and DATABASE[username].get("password") == password:
return DATABASE[username]
# Otherwise raise an exception
else:
raise UnauthorizedException()
def get_or_create_user(self, user: Dict) -> Dict:
""" Method to get user information from the database (or optionally create the user in the database).
"""
# If the user exists in our database, return the information
if user.get("username") in DATABASE:
return DATABASE[user["username"]]
# If the username is not in the database, raise an exception
else:
raise UnauthorizedException()
As you can see we’ve created a subclass of the BasicStrategy
and we’ve created methods that tell the strategy how to interact with the database. That’s it!
3. Protect your app
Now we’re ready to use the strategy to protect an application. To so do, we start with a Customs
object which will protect
our endpoints or creates a safe_zone
for a set of endpoints. Lets stick with a single endpoint for now.
from flask import Flask
from customs import Customs
# Create the app, and the customs
app = Flask(__name__)
customs = Customs(app)
# ... The strategy definition we've created before goes here
# Make an instance of our basic authentication strategy
basic_authentication = BasicAuthentication()
@app.route("/")
def index():
return "This is an open route, everyone has access"
@app.route("/protected")
@customs.protect(strategies=[basic_authentication])
def protected():
return "This is a protected route, users only!"
This app now has 2 routes. One is open to the world (http://localhost:5000/) and one is protected with basic authentication (http://localhost:5000/protected).
For full examples head over to the examples/
directory.
API¶
This page contains generated documentation from the Customs code, describing the available classes and methods.
Customs¶
-
class
customs.customs.
Customs
(*args, **kwargs)¶ Customs is a protective layer that makes sure every incoming request is properly authenticated and checked. Customs can define “safe zones”, like parts of your API, or protect individual routes. Customs is intended as middleware for Flask applications.
- Parameters
app (Flask) – The Flask application to mount this middleware on
use_sessions (bool, optional) – Whether or not to use sessions for storing user information. Defaults to True.
session_timeout (timedelta, optional) – The default expiration time for sessions. Defaults to timedelta(days=31).
user_class (Type, optional) – The class to use for parsing user information. Defaults to dict.
unauthorized_redirect_url (str, optional) – The URL to redirect to when a user tries to access an endpoint without proper authorization
Examples
>>> from flask import Flask >>> from customs import Customs >>> app = Flask(__name__) >>> customs = Customs(app)
-
protect
(strategies: Union[List[Union[str, customs.strategies.base_strategy.BaseStrategy]], str, customs.strategies.base_strategy.BaseStrategy]) → Callable¶ Decorator method that protects a specific route, using a set of strategies.
- Parameters
strategies (Union[List[Union[str, BaseStrategy]], str, BaseStrategy]) – The strategy or list of strategies to use for protection. Can be a list of strategy names, list of strategy objects or an individual strategy by name or as object.
- Returns
The wrapped view function
- Return type
Callable
Examples
>>> @app.route("/test") ... @customs.protect(strategies=["basic"]) ... def test_route(): ... return "Success"
-
register_strategy
(name: str, strategy: customs.strategies.base_strategy.BaseStrategy) → customs.customs.Customs¶ Register a strategy without using it for every route. Makes the strategy available by its name. Most strategies auto-register, so this method should not be needed very often.
- Parameters
name (str) – The name of the strategy
strategy (BaseStrategy) – The strategy (which should inherit from BaseStrategy)
- Returns
Returns this instance of customs for chaining
- Return type
-
safe_zone
(zone: Union[flask.blueprints.Blueprint, flask.app.Flask], strategies: List[Union[str, customs.strategies.base_strategy.BaseStrategy]])¶ Create a zone/section of the app that is protected with specific strategies. The zone can be an entire Flask application, or a section of the app in the form of a Blueprint.
- Parameters
zone (Union[Blueprint, Flask]) – The zone to protect
strategies (List[str]) – The names of the strategies to use
- Returns
The (now protected) input zone
- Return type
Union[Blueprint, Flask]
Examples
>>> from flask import Flask >>> from customs import Customs >>> app = Flask(__name__) >>> customs = Customs(app) >>> # Define routes here ... >>> customs.safe_zone(app, strategies=["basic"])
Strategies¶
Strategies define how the customs should protect a resource (endpoint, blueprint, or app). Strategies can be combined to create the desired protection. Strategies should be subclassed to tell them about application specific element, for example how to read a user from the database.
-
class
customs.strategies.
BasicStrategy
¶ Strategy that enables authorization using the “basic” authorization header.
Examples
>>> class BasicAuthentication(BasicStrategy): ... def get_or_create_user(self, user: Dict) -> Dict: ... if user.get("username") in DATABASE: ... return DATABASE[user["username"]] ... else: ... raise UnauthorizedException() ... def validate_credentials(self, username: str, password: str) -> Dict: ... if username in DATABASE and DATABASE[username].get("password") == password: ... return DATABASE[username] ... else: ... raise UnauthorizedException()
-
authenticate
(request: Union[werkzeug.wrappers.request.Request, flask.wrappers.Request]) → Any¶ Method that will extract the basic authorization header from the request, and will then call the validate_credentials method with a username and password. The validate_credentials method should be implemented by the user. This method is called by Customs internally and is not intended for external use.
- Parameters
request (Union[Request, FlaskRequest]) – The incoming request (usually a Flask request)
- Raises
UnauthorizedException – Raised when the user is not authorized (invalid or missing credentials)
- Returns
The user information
- Return type
Dict
-
-
class
customs.strategies.
FacebookStrategy
(client_id: str, client_secret: str, scopes: Optional[List[str]] = None, enable_insecure: bool = False, endpoint_prefix: Optional[str] = None)¶ Authentication using Facebook as an OAuth2 provider.
-
get_user_info
() → Dict¶ Method to get user info for the logged in user.
- Raises
UnauthorizedException – When the user is not authenticated
- Returns
The user profile
- Return type
Dict
-
-
class
customs.strategies.
GithubStrategy
(client_id: str, client_secret: str, scopes: Optional[List[str]] = None, enable_insecure: bool = False, endpoint_prefix: Optional[str] = None)¶ Authentication using Github as an OAuth2 provider.
-
validate_token
() → Dict¶ Method to validate a Github token with Github.
- Raises
UnauthorizedException – When the user isn’t authenticated or token is not valid
- Returns
The data from the token
- Return type
Dict
-
-
class
customs.strategies.
GoogleStrategy
(client_id: str, client_secret: str, scopes: Optional[List[str]] = None, enable_insecure: bool = False, endpoint_prefix: Optional[str] = None)¶ Authentication using Google as an OAuth2 provider.
-
validate_token
() → Dict¶ Method to validate a Google token with Google.
- Raises
UnauthorizedException – When the user isn’t authenticated or token is not valid
- Returns
The data from the token
- Return type
Dict
-
-
class
customs.strategies.
JWTStrategy
(key: Optional[str] = None)¶ Authentication using JWT tokens.
-
authenticate
(request: Union[werkzeug.wrappers.request.Request, flask.wrappers.Request]) → Any¶ Method that will extract the JWT authorization header from the request, and will then call the deserialize_user method with the decoded content of the token. The validate_credentials method should be implemented by the user. This method is called by Customs internally and is not intended for external use.
- Parameters
request (Union[Request, FlaskRequest]) – The incoming request (usually a Flask request)
- Raises
UnauthorizedException – Raised when the user is not authorized (invalid or missing credentials)
- Returns
The user information
- Return type
Dict
-
sign
(user: Any) → str¶ Sign a new token for the user. Serialize the user info before signing.
- Parameters
user (Any) – The user data to serialize and sign
- Returns
The signed token
- Return type
str
-
Development¶
Initialize GitHooks¶
Git hooks can be used to automate certain version control steps, e.g. run tests and style checks before committing. Git hooks for this repository can be found at ./.githooks
, but have to be initialized manually on the development machine with this command:
chmod +x .githooks/prepare-commit-msg
git config core.hooksPath .githooks
License¶
MIT License
Copyright (c) 2021 Gijs Wobben
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.