-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Flesh out generic flow documentation
- Loading branch information
1 parent
1d42f68
commit 04863a9
Showing
1 changed file
with
100 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,109 @@ | ||
Authentication Flow | ||
=================== | ||
|
||
This is a brief, framework-agnostic overview of how to use Authl. If you want to use Authl with `Flask`_, instead consider using the :py:mod:`authl.flask` wrapper. | ||
This is a brief, framework-agnostic overview of how to use Authl. If you want to | ||
use Authl with `Flask`_, instead consider using the :py:mod:`authl.flask` | ||
wrapper. | ||
|
||
.. _Flask: https://flask.palletsprojects.com/ | ||
|
||
Typically you will simply use :py:func:`authl.from_config` to build an instance with your configured handlers. However, you can also instance it and your handlers directly. See the documentation for :py:class:`Authl` and :py:func:`Authl.add_handler`, as well as the documentation for :py:mod:`authl.handlers`. | ||
Notably, the example code below is not written against any specific framework | ||
and is just to be used as a rough example of how it might look. | ||
|
||
For the login flow, you need two parts: a login form, and a callback handler. The login form, at its simplest, should call :py:func:`Authl.get_handler_for_url` with the user's login URL to get the appropriate handler, and then call the handler's :py:func:`handlers.Handler.initiate_auth` function. The ``callback_uri`` argument needs to be able to map back to the handler in some way; typically you will include the handler's ``cb_id`` in the URL, either as a query parameter or as a path component. ``get_handler_for_url`` will then return a :py:class:`disposition.Disposition` object which should then direct the client in some way. Typically this will be either a :py:class:`disposition.Redirect` or a :py:class:`disposition.Notify`, but any of the disposition types are possible. | ||
Typically you will simply use :py:func:`authl.from_config` to build an instance | ||
with your configured handlers. However, you can also instance it and your | ||
handlers directly. See the documentation for :py:class:`Authl` and | ||
:py:func:`Authl.add_handler`, as well as the documentation for | ||
:py:mod:`authl.handlers`. | ||
|
||
The callback then must look up the associated handler and pass the request URL, the parsed ``GET`` arguments (if any), and the parsed ``POST`` arguments (if any) to the handler's :py:func:`handlers.Handler.check_callback` method. The resulting :py:class:`disposition.Disposition` object then indicates what comes next. Typically this will be either a :py:class:`disposition.Error` or a :py:class:`disposition.Verified`, but again, any disposition type is possible and must be handled accordingly. | ||
For the login flow, you need two parts: a login form, and a callback handler. | ||
|
||
Authl handlers also provide a few mechanisms that allow for an improved user experience; for example, :py:func:`authl.handlers.Handler.service_name` and :py:func:`authl.handlers.Handler.url_schemes` can be used to build out form elements that provide more information about which handlers are available, and :py:func:`authl.Authl.get_handler_for_url` can be used to implement an interactive "URL tester" to tell users in real-time whether the URL they're entering is a valid identity. This functionality is all expressed in the :py:mod:`authl.flask` implementation and should absolutely be replicated in any other frontend implementation. | ||
The login form should, at the very least, have a text input field for users to | ||
enter their identity URL, and should track the final post-login redirection | ||
target. | ||
|
||
When the form is submitted, it calls | ||
:py:func:`Authl.get_handler_for_url` with the user's login URL to get the | ||
appropriate handler, and then call the handler's | ||
:py:func:`handlers.Handler.initiate_auth` function. The ``callback_uri`` | ||
argument needs to be able to map back to the handler in some way; typically you | ||
will include the handler's ``cb_id`` in the URL, either as a query parameter or | ||
as a path component. ``get_handler_for_url`` will then return a | ||
:py:class:`disposition.Disposition` object which should then direct the client | ||
in some way. Typically this will be either a :py:class:`disposition.Redirect` or | ||
a :py:class:`disposition.Notify`, but any of the disposition types are possible. | ||
|
||
The callback then must look up the associated handler and pass the request URL, | ||
the parsed ``GET`` arguments (if any), and the parsed ``POST`` arguments (if | ||
any) to the handler's :py:func:`handlers.Handler.check_callback` method. The | ||
resulting :py:class:`disposition.Disposition` object then indicates what comes | ||
next. Typically this will be either a :py:class:`disposition.Error` or a | ||
:py:class:`disposition.Verified`, but again, any disposition type is possible | ||
and must be handled accordingly. | ||
|
||
Example (pseudo-)code follows: | ||
|
||
.. code-block:: python | ||
def handle_disposition(disp): | ||
if isinstance(disp, disposition.Redirect): | ||
return redirect(disp.url) | ||
if isinstance(disp, disposition.Verified): | ||
set_user_session(username=disp.identity) | ||
return redirect(disp.redir) | ||
if isinstance(disp, disposition.Notify): | ||
return render_notification_page(message=disp.cdata) | ||
if isinstance(disp, disposition.Error): | ||
return render_login_form(error=disp.message, redir=disp.redir) | ||
raise RuntimeError("Unknown disposition type " + disp) | ||
def handle_login_form(request): | ||
# The login form should have some means of providing the post-login | ||
# redirection URL | ||
redir_url = get_redir_url(request) | ||
# Get the submitted user identity; it's a good idea to support both | ||
# GET and POST arguments for this to let people bookmark a quick | ||
# login URL if they so desire | ||
me_url = request.args.get('me', request.post.get('me')) | ||
if me_url: | ||
handler, hid, id_url = authl_instance.get_handler_for_url(me_url) | ||
if handler: | ||
# get_callback_url is implemented by the app, and produces a URL | ||
# that can map to a handler by handler ID | ||
cb_url = get_callback_url(hid) | ||
# handle_disposition is implemented by the app, and handles the | ||
# result of an authentication step | ||
return handle_disposition( | ||
handler.initiate_auth(id_url, cb_url, redir_url)) | ||
return render_login_form( | ||
error="Unknown authentication method" if me_url else None, | ||
redir=redir_url) | ||
def handle_callback(request): | ||
hid = get_hid_from_url(request.url) | ||
handler = authl_instance.get_handler_by_id(hid) | ||
if not handler: | ||
return render_login_page(error="Invalid callback") | ||
return handle_disposition(handler.check_callback(request.url, | ||
request.args, | ||
request.post)) | ||
Login form UX | ||
------------- | ||
|
||
Authl handlers also provide a few mechanisms that allow for an improved user | ||
experience; for example, :py:func:`authl.handlers.Handler.service_name` and | ||
:py:func:`authl.handlers.Handler.url_schemes` can be used to build out form | ||
elements that provide more information about which handlers are available, and | ||
:py:func:`authl.Authl.get_handler_for_url` can be used to implement an | ||
interactive "URL tester" to tell users in real-time whether the URL they're | ||
entering is a valid identity. This functionality is all expressed in the | ||
:py:mod:`authl.flask` implementation and should absolutely be replicated in any | ||
other frontend implementation. | ||
|
||
See the `default Flask login template | ||
<https://github.com/PlaidWeb/Authl/blob/main/authl/flask_templates/login.html>`_ | ||
for an example of how this might look. |