Authentication
This module provides a simple setup and helper methods to authenticate users either based on HTTP Authorization header, query parameter or a cookie based session.
This addon can be included like this:
from flask import Flask
from flask_tern import auth
app = Flask(__name__)
# here is a good place to set app.config values if needed.
auth.init_app(app)
flask_tern.auth.init_app() configures authlib's flask integration.
The authlib extension instance is available anytime as global object importable as:
from flask_tern.auth import oauth
# The default client is registered as "oidc"
oauth.oidc
This instance allows access to current valid tokens and other token related methods.
How it works
Main entry point to user authentication is the local proxy flask_tern.auth.current_user. This proxy defers authentication attemps to
flask_tern.auth.utils.get_authorization(), which first looks for a HTTP Authorization header, which it decodes. If the header is available
it will be parsed and authentication type (e.g. basic, bearer, etc…), and data will be extracted.
If this header is not set, it will look for a X-Api-Key header.
As a last attempt it will check whether there is a session (cookie) with valid login data.
Header formats are as usual for basic aund bearer method. The custom apikey-v1 authorization scheme ose not use base64 encoding. The accepted value for the X-Api-Key is the same as for apikey-v1 auth scheme.
Based on authentication method, the app will look into USERINFO_MAP to parse and validate authentication data.
If all goes well, current_user will be a dictionary with authenticated and validated user information.
The whole authentication process also sets some request global variables in flask.g:
g.current_user: a
flask_tern.auth.user.Userobject with information about the current authenticatet user or Noneg.auth_type: the authentication type basic, bearer, apikey-v1, session
g.auth_info: the full authentication as extracted by
flask_tern.auth.utils.get_authorization()
This whole auth process is triggered once per request by accessing flask_tern.auth.current_user.
View Decorators
The following is a list of view decorators provided by flask_tern.auth. These decorators can be used to enforce authentication for view methods.
All view decorators access flask_tern.auth.current_user which kicks off the authentication process.
- flask_tern.auth.require_user(view_func: Callable) Callable[[], Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int, Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | WSGIApplication]
View decorator which enforces an authenticated user.
If
flask_tern.auth.current_useris not set, then callflask.abort(), which raises a 403 HTTP Exception. If we have a valid user, then execute the wrapped view method:@require_user def view(): return "ok"
# noqa: DAR201, DAR101
- Parameters:
view_func (Callable)
- Return type:
Callable[[], Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], Headers | Mapping[str, str | List[str] | Tuple[str, …]] | Sequence[Tuple[str, str | List[str] | Tuple[str, …]]]] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int, Headers | Mapping[str, str | List[str] | Tuple[str, …]] | Sequence[Tuple[str, str | List[str] | Tuple[str, …]]]] | WSGIApplication]
- flask_tern.auth.require_login(view_func: Callable) Callable[[], Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int, Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | WSGIApplication]
Redirect to login page if not logged in.
If
flask_tern.auth.current_useris not set, then it will generate a redirect to a named endpointoidc_login.login(as defined in blueprint flask_tern.auth.login.oidc_login). The current url is passed as url parameter return_url to the endpoint. After successful login, the user can be redirected back to the original url. If the user is not viewing from the web browser, we can return a 403 response.This wrapper can only be used with simple GET requests, as passing a post body on redirect may not work:
@require_login def view(): return "ok"
# noqa: DAR101, DAR201
- Parameters:
view_func (Callable)
- Return type:
Callable[[], Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], Headers | Mapping[str, str | List[str] | Tuple[str, …]] | Sequence[Tuple[str, str | List[str] | Tuple[str, …]]]] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int, Headers | Mapping[str, str | List[str] | Tuple[str, …]] | Sequence[Tuple[str, str | List[str] | Tuple[str, …]]]] | WSGIApplication]
- flask_tern.auth.require_roles(roles: Iterable[str]) Callable[[Callable], Callable]
Require an authenticated user with a specific set of roles.
This wrapper ensures that there is an authenticated user, and all roles set in this decorator are present on the current users role attribute:
@require_roles(["admin", "writer"]) def view(): return "ok"
# noqa: DAR101, DAR201
Auth Settings
Authentication setup requires a few configuration settings.
General
- flask_tern.auth.settings.USERINFO_MAP
A set of methods to extract user information based on authentication method. E.g. bearer looks at HTTP Authorization header with bearer tokens, and session uses sessions and cookies to store curretn login status. Other options can easily be implemented. See Authentication for details
This dictionary is used to extract user details from request depending on authentication method.
Auth method |
default extractor |
|---|---|
bearer |
|
session |
|
apikey-v1 |
OpenID Connect
- flask_tern.auth.settings.OIDC_DISCOVERY_URL = None
The URL which serves an OIDC discovery document. This usually ends with /.well-known/openid-configuration.
- flask_tern.auth.settings.OIDC_CLIENT_ID = None
OIDC client id
- flask_tern.auth.settings.OIDC_CLIENT_SECRET = None
OIDC client secret
- flask_tern.auth.settings.OIDC_SCOPE = 'openid profile email'
OIDC scope. This needs to include openid to enable OpenID Connect protocol.
- flask_tern.auth.settings.OIDC_USE_REFRESH_TOKEN = False
If set to true, the application will store a refresh token in the session, and will keep the access and id token valid (until refresh token or session timeout)
Api Key Authentication
- flask_tern.auth.settings.APIKEY_SERVICE = None
5000/api/v1.0. The URL must not end with /.
- Type:
The API endpoint of the API Key service e.g. http
- Type:
//localhost
Authentication methods
This section describes the supported authentication methods in detail.
OpenID Connect Session
The module flask_tern.auth.login provides a flask Blueprint to support session based login via browsers as well.
from flask_tern.auth.login import oidc_login
app = Flask(__name__)
app.register_blueprint(oidc_login, url_prefix="/api/oidc")
This setup will configure 3 url endpoints.
/api/oidc/login: start OIDC login dance
/api/oidc/authorize: return url from identity provider
/api/oidc/logout: clears current users session triggering a logout
See the respective view functions:
- flask_tern.auth.login.login()
Generate redirect url for oauth login.
accepts url parameter return_url or uses Referrer header to remember return_url
If there is no return_url set, it use the url for a route named
root, which needs to be configured on the Flask app.- Returns:
Redirect to authorize url.
- flask_tern.auth.login.authorize()
Parse and validate authorization response.
On success return redirect user back to original URL and set session cookie
If there is no return_url set, it use the url for a route named
root, which needs to be configured on the Flask app.- Returns:
Redirect to stored return url, or application root route.
- flask_tern.auth.login.logout()
Clear logged in session.
If there is no return_url set, it use the url for a route named
root, which needs to be configured on the Flask app.- Returns:
Redirect to return url.
After successful login, authenticated user info will be stored in the session under key oidc.user_info. Best way to retrieve this data is by accessing
flask_tern.auth.current_user.
If the setting OIDC_USE_REFRESH_TOKEN is enable (set to True), the application will keep OIDC id-, acces-, and refresh token up to date in the session. This means that the application code can use these tokens anytime to call out to additional services accepting these tokens. If token refresh is enabled, all current tokens are stored under oidc.tokens in the session, from where they are picked up by the authlib Flask integration.
If the view flask_tern.auth.login.authorize() fails to authorise the OIDC token it will return a redirect to either previous url, or application root.
Additionally a cookie auth_error will be set in the response carrying the error message.
Session handling
The application integrates Flask-Session which offers a configurable session backend for flask applications.
Please see Flask-Session Configuration section for details.
Common configuration values:
- flask_tern.settings.SECRET_KEY = os.urandom(16)
value to use to sign / encrypt session cookie. If unset, the app will auto generate a value. This value needs to be configured if load balancing across multiple processes is used.
- flask_tern.settings.SESSION_TYPE = None
Session backend type. The default is to use cookies as session storage.
- flask_tern.settings.SESSION_FILE_DIR = '/Users/uqgweis/Documents/Projects/workspaces/bioimages/flask_tern/docs/flask_session'
If SESSION_TYPE = “filesystem”, this option specifies the storage location for session data. as default it uses the flask_session folder within current working directory.
- flask_tern.settings.SESSION_FILE_THRESHOLD = 500
If SESSION_TYPE = “filesystem”, this option sets the maximum number of items the session stores before it starts deleting some. default is 500.
- flask_tern.settings.SESSION_FILE_MODE = 384
If ‘SESSION_TYPE = “filesystem”` this option sets the file mode for the session files. as default it uses 0o600. If given as string it will be parsed as integer or if prefixed with 0o as octal number.
- flask_tern.settings.SESSION_REDIS = None
If SESSION_TYPE = “redis”, this option should be a configured
redis.Redisinstance. If loaded from environment variables, it can be specified as connection url as described inredis:redis.from_url()
OAuth2 Bearer token
A request may contain an OAuth2 bearer token in the Authorization header. It assumed that the token provided is a JWT encoded access_token (as provided by keycloak) and user info can be extracted directly from the token.
See flask_tern.auth.oidc.get_oauth_userinfo() or details.
API Key
This method relies on a deployed API Key service. This method asks the API Key service to validate the given token. The API Key service is expected to return user info and roles, in the same format as Keycloak produces access tokens.
This method utilises flask_tern.cache to cache validation results. This is done to improve response time of subsequent request with API Keys.
To enable caching it is necessary to configure Flask-Caching properly.
# e.g.: use simple caching
CACHE_TYPE = "SimpleCache"
See flask_tern.auth.apikey.get_userinfo_from_apikey() for details.