-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
improved JWT and Token auth docs with examples (#221)
* improved JWT and Token auth docs with examples * fix CodeQL notices * added `token_login` endpoint, and more source code links --------- Co-authored-by: Daniel Townsend <dan@dantownsend.co.uk>
- Loading branch information
1 parent
1efb862
commit f560820
Showing
18 changed files
with
600 additions
and
214 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 |
---|---|---|
@@ -0,0 +1,91 @@ | ||
Endpoints | ||
========= | ||
|
||
An endpoint is provided for JWT login, and is designed to integrate with an | ||
ASGI app, such as Starlette or FastAPI. | ||
|
||
------------------------------------------------------------------------------- | ||
|
||
jwt_login | ||
--------- | ||
|
||
This creates an endpoint for logging in, and getting a JSON Web Token (JWT). | ||
|
||
.. code-block:: python | ||
from piccolo_api.jwt_auth.endpoints import jwt_login | ||
from starlette import Starlette | ||
from starlette.routing import Route, Router | ||
app = Starlette( | ||
routes=[ | ||
Route( | ||
path="/login/", | ||
endpoint=jwt_login( | ||
secret='mysecret123' | ||
) | ||
), | ||
] | ||
) | ||
secret | ||
~~~~~~ | ||
|
||
This is used for signing the JWT. | ||
|
||
expiry | ||
~~~~~~ | ||
|
||
An optional argument, which allows you to control when a token expires. By | ||
default it's set to 1 day. | ||
|
||
.. code-block:: python | ||
from datetime import timedelta | ||
jwt_login( | ||
secret='mysecret123', | ||
expiry=timedelta(minutes=10) | ||
) | ||
.. hint:: You generally want short expiry tokens for web applications, and | ||
longer expiry times for mobile applications. | ||
|
||
.. hint:: See :class:`JWTMiddleware <piccolo_api.jwt_auth.middleware.JWTMiddleware>` | ||
for how to protect your endpoints. | ||
|
||
------------------------------------------------------------------------------- | ||
|
||
Usage | ||
----- | ||
|
||
You can use any HTTP client to get the JWT token. In our example we use ``curl``. | ||
|
||
To get a JWT token: | ||
|
||
.. code-block:: shell | ||
curl -X POST \ | ||
-H "Content-Type: application/json" \ | ||
-d '{"username": "piccolo", "password": "piccolo123"}' \ | ||
http://localhost:8000/login/ | ||
To get data from a protected endpoint: | ||
|
||
.. code-block:: shell | ||
curl -H "Authorization: Bearer your-JWT-token" \ | ||
http://localhost:8000/private/movies/ | ||
.. hint:: You can use all ``HTTP`` methods by passing a valid JWT token in the ``Authorization`` header. | ||
|
||
------------------------------------------------------------------------------- | ||
|
||
Source | ||
------ | ||
|
||
.. currentmodule:: piccolo_api.jwt_auth.endpoints | ||
|
||
.. autofunction:: jwt_login |
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 |
---|---|---|
@@ -0,0 +1,22 @@ | ||
Full Example | ||
============ | ||
|
||
Let's combine all of previous examples into a complete app. | ||
|
||
------------------------------------------------------------------------------- | ||
|
||
FastAPI | ||
------- | ||
|
||
.. include:: ./examples/example.py | ||
:code: python | ||
|
||
------------------------------------------------------------------------------- | ||
|
||
Starlette | ||
--------- | ||
|
||
Is almost identical to the FastAPI example - just replace ``FastAPI`` with | ||
``Starlette``, and use Starlette's ``HTTPEndpoint`` for your endpoints. | ||
You also need to write your own crud endpoints because ``Starlette`` | ||
can't use ``FastAPIWrapper``. An example is in `SessionAuth <https://piccolo-api.readthedocs.io/en/latest/session_auth/example.html>`_. |
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 |
---|---|---|
@@ -0,0 +1,65 @@ | ||
from datetime import timedelta | ||
|
||
from fastapi import FastAPI, Request | ||
from home.tables import Movie # An example Table | ||
from piccolo_admin.endpoints import create_admin | ||
from piccolo_api.crud.endpoints import PiccoloCRUD | ||
from piccolo_api.fastapi.endpoints import FastAPIKwargs, FastAPIWrapper | ||
from piccolo_api.jwt_auth.endpoints import jwt_login | ||
from piccolo_api.jwt_auth.middleware import JWTBlacklist, JWTMiddleware | ||
from starlette.routing import Mount, Route | ||
|
||
public_app = FastAPI( | ||
routes=[ | ||
Mount( | ||
"/admin/", | ||
create_admin(tables=[Movie]), | ||
), | ||
Route( | ||
path="/login/", | ||
endpoint=jwt_login( | ||
secret="mysecret123", | ||
expiry=timedelta(minutes=60), # default is 1 day | ||
), | ||
), | ||
], | ||
) | ||
|
||
|
||
BLACKLISTED_TOKENS = [] | ||
|
||
|
||
class MyBlacklist(JWTBlacklist): | ||
async def in_blacklist(self, token: str) -> bool: | ||
return token in BLACKLISTED_TOKENS | ||
|
||
|
||
private_app = FastAPI() | ||
|
||
protected_app = JWTMiddleware( | ||
private_app, | ||
auth_table=BaseUser, | ||
secret="mysecret123", | ||
blacklist=MyBlacklist(), | ||
) | ||
|
||
FastAPIWrapper( | ||
"/movies/", | ||
fastapi_app=private_app, | ||
piccolo_crud=PiccoloCRUD(Movie, read_only=False), | ||
fastapi_kwargs=FastAPIKwargs( | ||
all_routes={"tags": ["Movies"]}, | ||
), | ||
) | ||
|
||
public_app.mount("/private", protected_app) | ||
|
||
# This is optional if you want to provide a logout endpoint | ||
# in your application. By adding a token to the token blacklist, | ||
# you are invalidating the token and need to login again to get | ||
# new valid token | ||
@private_app.get("/logout/") | ||
async def logout(request: Request) -> None: | ||
BLACKLISTED_TOKENS.append( | ||
request.headers.get("authorization").split(" ")[-1] | ||
) |
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,104 +1,12 @@ | ||
.. _JWT: | ||
.. _JWTAuth: | ||
|
||
JWT | ||
=== | ||
JWT Auth | ||
======== | ||
|
||
Introduction | ||
------------ | ||
.. toctree:: | ||
:maxdepth: 1 | ||
|
||
JWT is a token format, often used for authentication. | ||
|
||
------------------------------------------------------------------------------- | ||
|
||
jwt_login | ||
--------- | ||
|
||
This creates an endpoint for logging in, and getting a JSON Web Token (JWT). | ||
|
||
.. code-block:: python | ||
from piccolo_api.jwt_auth.endpoints import jwt_login | ||
from starlette import Starlette | ||
from starlette.routing import Route, Router | ||
app = Starlette( | ||
routes=[ | ||
Route( | ||
path="/login/", | ||
endpoint=jwt_login( | ||
secret='mysecret123' | ||
) | ||
), | ||
] | ||
) | ||
Required arguments | ||
~~~~~~~~~~~~~~~~~~ | ||
|
||
You have to pass in two arguments: | ||
|
||
* auth_table - a subclass of Piccolo's ``BaseUser`` class, which is used to | ||
authenticate the user. | ||
* secret - this is used for signing the JWT. | ||
|
||
expiry | ||
~~~~~~ | ||
|
||
An optional argument, which allows you to control when a token expires. By | ||
default it's set to 1 day. | ||
|
||
.. code-block:: python | ||
from datetime import timedelta | ||
jwt_login( | ||
secret='mysecret123', | ||
expiry=timedelta(minutes=10) | ||
) | ||
.. hint:: You generally want short expiry tokens for web applications, and | ||
longer expiry times for mobile applications. | ||
|
||
.. hint:: See ``JWTMiddleware`` for how to protect your endpoints. | ||
|
||
------------------------------------------------------------------------------- | ||
|
||
JWTMiddleware | ||
------------- | ||
|
||
This wraps an ASGI app, and ensures a valid token is passed in the header. | ||
Otherwise a 403 error is returned. If the token is valid, the corresponding | ||
``user_id`` is added to the ``scope``. | ||
|
||
blacklist | ||
~~~~~~~~~ | ||
|
||
Optionally, you can pass in a ``blacklist`` argument, which is a subclass of | ||
:class:`JWTBlacklist`. The implementation of the ``in_blacklist`` method is up to | ||
the user - the data could come from a database, a file, a Python list, or | ||
anywhere else. | ||
|
||
.. code-block:: python | ||
# An example blacklist. | ||
BLACKLISTED_TOKENS = ['abc123', 'def456'] | ||
class MyBlacklist(JWTBlacklist): | ||
async def in_blacklist(self, token: str) -> bool: | ||
return token in BLACKLISTED_TOKENS | ||
asgi_app = JWTMiddleware( | ||
my_endpoint, | ||
auth_table=User, | ||
secret='mysecret123', | ||
blacklist=MyBlacklist() | ||
) | ||
.. hint:: Blacklists are important if you have tokens with a long expiry date. | ||
|
||
.. todo - show example POST using requests | ||
./introduction | ||
./endpoints | ||
./middleware | ||
./example |
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 |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Introduction | ||
============ | ||
|
||
JSON Web Token (JWT) is a token format, often used for authentication. For more information | ||
see the `jwt.io website <https://jwt.io/introduction>`_. |
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 |
---|---|---|
@@ -0,0 +1,62 @@ | ||
.. _JWTMiddleware: | ||
|
||
Middleware | ||
========== | ||
|
||
This middleware protects endpoints using JWT tokens. | ||
|
||
------------------------------------------------------------------------------- | ||
|
||
Setup | ||
----- | ||
|
||
``JWTMiddleware`` wraps an ASGI app, and ensures a valid token is passed in the header. | ||
Otherwise a 403 error is returned. If the token is valid, the corresponding | ||
``user_id`` is added to the ``scope``. | ||
|
||
blacklist | ||
~~~~~~~~~ | ||
|
||
Optionally, you can pass in a ``blacklist`` argument, which is a subclass of | ||
:class:`JWTBlacklist`. The implementation of the ``in_blacklist`` method is up to | ||
the user - the data could come from a database, a file, a Python list, or | ||
anywhere else. | ||
|
||
.. code-block:: python | ||
# An example blacklist. | ||
BLACKLISTED_TOKENS = ['abc123', 'def456'] | ||
class MyBlacklist(JWTBlacklist): | ||
async def in_blacklist(self, token: str) -> bool: | ||
return token in BLACKLISTED_TOKENS | ||
asgi_app = JWTMiddleware( | ||
my_endpoint, | ||
auth_table=User, | ||
secret='mysecret123', | ||
blacklist=MyBlacklist() | ||
) | ||
.. hint:: Blacklists are important if you have tokens with a long expiry date. | ||
|
||
------------------------------------------------------------------------------- | ||
|
||
Source | ||
------ | ||
|
||
JWTMiddleware | ||
~~~~~~~~~~~~~~ | ||
|
||
.. currentmodule:: piccolo_api.jwt_auth.middleware | ||
|
||
.. autoclass:: JWTMiddleware | ||
|
||
JWTBlacklist | ||
~~~~~~~~~~~~ | ||
|
||
.. autoclass:: JWTBlacklist | ||
:members: |
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
Oops, something went wrong.