├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── app ├── JWTBearer.py ├── __init__.py ├── auth.py ├── service.py └── user_handlers.py ├── poetry.lock ├── pyproject.toml ├── setup.sh └── start.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | .idea 107 | .DS_Store 108 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/ambv/black 3 | rev: stable 4 | hooks: 5 | - id: black 6 | language_version: python3.7 7 | - repo: https://github.com/pre-commit/pre-commit-hooks 8 | rev: v1.2.3 9 | hooks: 10 | - id: flake8 11 | args: ["--ignore=E203,W503,E501"] 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Johannes Gontrum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgontrum/fastapi_jwt_auth_with_aws_cognito/5581435d41680c82b55bf64b42a099ebd1ece5c0/README.md -------------------------------------------------------------------------------- /app/JWTBearer.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Optional, List 2 | 3 | from fastapi import HTTPException 4 | from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials 5 | from jose import jwt, jwk, JWTError 6 | from jose.utils import base64url_decode 7 | from pydantic import BaseModel 8 | from starlette.requests import Request 9 | from starlette.status import HTTP_403_FORBIDDEN 10 | 11 | JWK = Dict[str, str] 12 | 13 | 14 | class JWKS(BaseModel): 15 | keys: List[JWK] 16 | 17 | 18 | class JWTAuthorizationCredentials(BaseModel): 19 | jwt_token: str 20 | header: Dict[str, str] 21 | claims: Dict[str, str] 22 | signature: str 23 | message: str 24 | 25 | 26 | class JWTBearer(HTTPBearer): 27 | def __init__(self, jwks: JWKS, auto_error: bool = True): 28 | super().__init__(auto_error=auto_error) 29 | 30 | self.kid_to_jwk = {jwk["kid"]: jwk for jwk in jwks.keys} 31 | 32 | def verify_jwk_token(self, jwt_credentials: JWTAuthorizationCredentials) -> bool: 33 | try: 34 | public_key = self.kid_to_jwk[jwt_credentials.header["kid"]] 35 | except KeyError: 36 | raise HTTPException( 37 | status_code=HTTP_403_FORBIDDEN, detail="JWK public key not found" 38 | ) 39 | 40 | key = jwk.construct(public_key) 41 | decoded_signature = base64url_decode(jwt_credentials.signature.encode()) 42 | 43 | return key.verify(jwt_credentials.message.encode(), decoded_signature) 44 | 45 | async def __call__(self, request: Request) -> Optional[JWTAuthorizationCredentials]: 46 | credentials: HTTPAuthorizationCredentials = await super().__call__(request) 47 | 48 | if credentials: 49 | if not credentials.scheme == "Bearer": 50 | raise HTTPException( 51 | status_code=HTTP_403_FORBIDDEN, detail="Wrong authentication method" 52 | ) 53 | 54 | jwt_token = credentials.credentials 55 | 56 | message, signature = jwt_token.rsplit(".", 1) 57 | 58 | try: 59 | jwt_credentials = JWTAuthorizationCredentials( 60 | jwt_token=jwt_token, 61 | header=jwt.get_unverified_header(jwt_token), 62 | claims=jwt.get_unverified_claims(jwt_token), 63 | signature=signature, 64 | message=message, 65 | ) 66 | except JWTError: 67 | raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="JWK invalid") 68 | 69 | if not self.verify_jwk_token(jwt_credentials): 70 | raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="JWK invalid") 71 | 72 | return jwt_credentials 73 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgontrum/fastapi_jwt_auth_with_aws_cognito/5581435d41680c82b55bf64b42a099ebd1ece5c0/app/__init__.py -------------------------------------------------------------------------------- /app/auth.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import requests 4 | from dotenv import load_dotenv 5 | from fastapi import Depends, HTTPException 6 | from starlette.status import HTTP_403_FORBIDDEN 7 | 8 | from app.JWTBearer import JWKS, JWTBearer, JWTAuthorizationCredentials 9 | 10 | load_dotenv() # Automatically load environment variables from a '.env' file. 11 | 12 | jwks = JWKS.parse_obj( 13 | requests.get( 14 | f"https://cognito-idp.{os.environ.get('COGNITO_REGION')}.amazonaws.com/" 15 | f"{os.environ.get('COGNITO_POOL_ID')}/.well-known/jwks.json" 16 | ).json() 17 | ) 18 | 19 | auth = JWTBearer(jwks) 20 | 21 | 22 | async def get_current_user( 23 | credentials: JWTAuthorizationCredentials = Depends(auth) 24 | ) -> str: 25 | try: 26 | return credentials.claims["username"] 27 | except KeyError: 28 | HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Username missing") 29 | -------------------------------------------------------------------------------- /app/service.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, FastAPI 2 | 3 | from app.JWTBearer import JWTBearer 4 | from app.auth import jwks 5 | from .user_handlers import router as user_router 6 | 7 | app = FastAPI() 8 | 9 | auth = JWTBearer(jwks) 10 | 11 | 12 | @app.get("/secure", dependencies=[Depends(auth)]) 13 | async def secure() -> bool: 14 | return True 15 | 16 | 17 | @app.get("/not_secure") 18 | async def not_secure() -> bool: 19 | return True 20 | 21 | 22 | app.include_router(user_router, prefix="/user", dependencies=[Depends(auth)]) 23 | -------------------------------------------------------------------------------- /app/user_handlers.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | 3 | from app.auth import get_current_user 4 | 5 | router = APIRouter() 6 | 7 | 8 | @router.get("/test") 9 | async def test(username: str = Depends(get_current_user)): 10 | return {"username": username} 11 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "dev" 3 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 4 | name = "appdirs" 5 | optional = false 6 | python-versions = "*" 7 | version = "1.4.3" 8 | 9 | [[package]] 10 | category = "dev" 11 | description = "A few extensions to pyyaml." 12 | name = "aspy.yaml" 13 | optional = false 14 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 15 | version = "1.3.0" 16 | 17 | [package.dependencies] 18 | pyyaml = "*" 19 | 20 | [[package]] 21 | category = "dev" 22 | description = "Classes Without Boilerplate" 23 | name = "attrs" 24 | optional = false 25 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 26 | version = "19.1.0" 27 | 28 | [[package]] 29 | category = "dev" 30 | description = "The uncompromising code formatter." 31 | name = "black" 32 | optional = false 33 | python-versions = ">=3.6" 34 | version = "18.9b0" 35 | 36 | [package.dependencies] 37 | appdirs = "*" 38 | attrs = ">=17.4.0" 39 | click = ">=6.5" 40 | toml = ">=0.9.4" 41 | 42 | [[package]] 43 | category = "main" 44 | description = "Python package for providing Mozilla's CA Bundle." 45 | name = "certifi" 46 | optional = false 47 | python-versions = "*" 48 | version = "2019.6.16" 49 | 50 | [[package]] 51 | category = "dev" 52 | description = "Validate configuration and produce human readable error messages." 53 | name = "cfgv" 54 | optional = false 55 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 56 | version = "2.0.1" 57 | 58 | [package.dependencies] 59 | six = "*" 60 | 61 | [[package]] 62 | category = "main" 63 | description = "Universal encoding detector for Python 2 and 3" 64 | name = "chardet" 65 | optional = false 66 | python-versions = "*" 67 | version = "3.0.4" 68 | 69 | [[package]] 70 | category = "main" 71 | description = "Composable command line interface toolkit" 72 | name = "click" 73 | optional = false 74 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 75 | version = "7.0" 76 | 77 | [[package]] 78 | category = "main" 79 | description = "ECDSA cryptographic signature library (pure python)" 80 | name = "ecdsa" 81 | optional = false 82 | python-versions = "*" 83 | version = "0.13.2" 84 | 85 | [[package]] 86 | category = "dev" 87 | description = "Discover and load entry points from installed packages." 88 | name = "entrypoints" 89 | optional = false 90 | python-versions = ">=2.7" 91 | version = "0.3" 92 | 93 | [[package]] 94 | category = "main" 95 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 96 | name = "fastapi" 97 | optional = false 98 | python-versions = ">=3.6" 99 | version = "0.36.0" 100 | 101 | [package.dependencies] 102 | pydantic = "0.30" 103 | starlette = ">=0.11.1,<=0.12.7" 104 | 105 | [[package]] 106 | category = "dev" 107 | description = "the modular source code checker: pep8, pyflakes and co" 108 | name = "flake8" 109 | optional = false 110 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 111 | version = "3.7.8" 112 | 113 | [package.dependencies] 114 | entrypoints = ">=0.3.0,<0.4.0" 115 | mccabe = ">=0.6.0,<0.7.0" 116 | pycodestyle = ">=2.5.0,<2.6.0" 117 | pyflakes = ">=2.1.0,<2.2.0" 118 | 119 | [[package]] 120 | category = "main" 121 | description = "Clean single-source support for Python 3 and 2" 122 | name = "future" 123 | optional = false 124 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 125 | version = "0.17.1" 126 | 127 | [[package]] 128 | category = "main" 129 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 130 | name = "h11" 131 | optional = false 132 | python-versions = "*" 133 | version = "0.8.1" 134 | 135 | [[package]] 136 | category = "main" 137 | description = "A collection of framework independent HTTP protocol utils." 138 | marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"pypy\"" 139 | name = "httptools" 140 | optional = false 141 | python-versions = "*" 142 | version = "0.0.13" 143 | 144 | [[package]] 145 | category = "dev" 146 | description = "File identification library for Python" 147 | name = "identify" 148 | optional = false 149 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 150 | version = "1.4.7" 151 | 152 | [[package]] 153 | category = "main" 154 | description = "Internationalized Domain Names in Applications (IDNA)" 155 | name = "idna" 156 | optional = false 157 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 158 | version = "2.8" 159 | 160 | [[package]] 161 | category = "dev" 162 | description = "Read metadata from Python packages" 163 | name = "importlib-metadata" 164 | optional = false 165 | python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3" 166 | version = "0.19" 167 | 168 | [package.dependencies] 169 | zipp = ">=0.5" 170 | 171 | [[package]] 172 | category = "dev" 173 | description = "McCabe checker, plugin for flake8" 174 | name = "mccabe" 175 | optional = false 176 | python-versions = "*" 177 | version = "0.6.1" 178 | 179 | [[package]] 180 | category = "dev" 181 | description = "More routines for operating on iterables, beyond itertools" 182 | name = "more-itertools" 183 | optional = false 184 | python-versions = ">=3.4" 185 | version = "7.2.0" 186 | 187 | [[package]] 188 | category = "dev" 189 | description = "Node.js virtual environment builder" 190 | name = "nodeenv" 191 | optional = false 192 | python-versions = "*" 193 | version = "1.3.3" 194 | 195 | [[package]] 196 | category = "dev" 197 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 198 | name = "pre-commit" 199 | optional = false 200 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 201 | version = "1.18.3" 202 | 203 | [package.dependencies] 204 | "aspy.yaml" = "*" 205 | cfgv = ">=2.0.0" 206 | identify = ">=1.0.0" 207 | importlib-metadata = "*" 208 | nodeenv = ">=0.11.1" 209 | pyyaml = "*" 210 | six = "*" 211 | toml = "*" 212 | virtualenv = ">=15.2" 213 | 214 | [[package]] 215 | category = "main" 216 | description = "ASN.1 types and codecs" 217 | name = "pyasn1" 218 | optional = false 219 | python-versions = "*" 220 | version = "0.4.6" 221 | 222 | [[package]] 223 | category = "dev" 224 | description = "Python style guide checker" 225 | name = "pycodestyle" 226 | optional = false 227 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 228 | version = "2.5.0" 229 | 230 | [[package]] 231 | category = "main" 232 | description = "Data validation and settings management using python 3.6 type hinting" 233 | name = "pydantic" 234 | optional = false 235 | python-versions = ">=3.6" 236 | version = "0.30" 237 | 238 | [[package]] 239 | category = "dev" 240 | description = "passive checker of Python programs" 241 | name = "pyflakes" 242 | optional = false 243 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 244 | version = "2.1.1" 245 | 246 | [[package]] 247 | category = "main" 248 | description = "Add .env support to your django/flask apps in development and deployments" 249 | name = "python-dotenv" 250 | optional = false 251 | python-versions = "*" 252 | version = "0.10.3" 253 | 254 | [[package]] 255 | category = "main" 256 | description = "JOSE implementation in Python" 257 | name = "python-jose" 258 | optional = false 259 | python-versions = "*" 260 | version = "3.0.1" 261 | 262 | [package.dependencies] 263 | ecdsa = "<1.0" 264 | future = "<1.0" 265 | rsa = "*" 266 | six = "<2.0" 267 | 268 | [[package]] 269 | category = "dev" 270 | description = "YAML parser and emitter for Python" 271 | name = "pyyaml" 272 | optional = false 273 | python-versions = "*" 274 | version = "5.1.2" 275 | 276 | [[package]] 277 | category = "main" 278 | description = "Python HTTP for Humans." 279 | name = "requests" 280 | optional = false 281 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 282 | version = "2.22.0" 283 | 284 | [package.dependencies] 285 | certifi = ">=2017.4.17" 286 | chardet = ">=3.0.2,<3.1.0" 287 | idna = ">=2.5,<2.9" 288 | urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" 289 | 290 | [[package]] 291 | category = "main" 292 | description = "Pure-Python RSA implementation" 293 | name = "rsa" 294 | optional = false 295 | python-versions = "*" 296 | version = "4.0" 297 | 298 | [package.dependencies] 299 | pyasn1 = ">=0.1.3" 300 | 301 | [[package]] 302 | category = "main" 303 | description = "Python 2 and 3 compatibility utilities" 304 | name = "six" 305 | optional = false 306 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 307 | version = "1.12.0" 308 | 309 | [[package]] 310 | category = "main" 311 | description = "The little ASGI library that shines." 312 | name = "starlette" 313 | optional = false 314 | python-versions = ">=3.6" 315 | version = "0.12.7" 316 | 317 | [[package]] 318 | category = "dev" 319 | description = "Python Library for Tom's Obvious, Minimal Language" 320 | name = "toml" 321 | optional = false 322 | python-versions = "*" 323 | version = "0.10.0" 324 | 325 | [[package]] 326 | category = "main" 327 | description = "HTTP library with thread-safe connection pooling, file post, and more." 328 | name = "urllib3" 329 | optional = false 330 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" 331 | version = "1.25.3" 332 | 333 | [[package]] 334 | category = "main" 335 | description = "The lightning-fast ASGI server." 336 | name = "uvicorn" 337 | optional = false 338 | python-versions = "*" 339 | version = "0.8.6" 340 | 341 | [package.dependencies] 342 | click = ">=7.0.0,<8.0.0" 343 | h11 = ">=0.8.0,<0.9.0" 344 | httptools = "0.0.13" 345 | uvloop = ">=0.12.0,<0.13.0" 346 | websockets = ">=7.0.0,<8.0.0" 347 | 348 | [[package]] 349 | category = "main" 350 | description = "Fast implementation of asyncio event loop on top of libuv" 351 | marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"pypy\"" 352 | name = "uvloop" 353 | optional = false 354 | python-versions = "*" 355 | version = "0.12.2" 356 | 357 | [[package]] 358 | category = "dev" 359 | description = "Virtual Python Environment builder" 360 | name = "virtualenv" 361 | optional = false 362 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 363 | version = "16.7.4" 364 | 365 | [[package]] 366 | category = "main" 367 | description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" 368 | name = "websockets" 369 | optional = false 370 | python-versions = ">=3.4" 371 | version = "7.0" 372 | 373 | [[package]] 374 | category = "dev" 375 | description = "Backport of pathlib-compatible object wrapper for zip files" 376 | name = "zipp" 377 | optional = false 378 | python-versions = ">=2.7" 379 | version = "0.6.0" 380 | 381 | [package.dependencies] 382 | more-itertools = "*" 383 | 384 | [metadata] 385 | content-hash = "1b6e943400a18f1f53314c0a1dfd6fe2207ffa2346f8797ef34422f17df74a8a" 386 | python-versions = "^3.7" 387 | 388 | [metadata.hashes] 389 | appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] 390 | "aspy.yaml" = ["463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc", "e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"] 391 | attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"] 392 | black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"] 393 | certifi = ["046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", "945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"] 394 | cfgv = ["edb387943b665bf9c434f717bf630fa78aecd53d5900d2e05da6ad6048553144", "fbd93c9ab0a523bf7daec408f3be2ed99a980e20b2d19b50fc184ca6b820d289"] 395 | chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] 396 | click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] 397 | ecdsa = ["20c17e527e75acad8f402290e158a6ac178b91b881f941fc6ea305bfdfb9657c", "5c034ffa23413ac923541ceb3ac14ec15a0d2530690413bff58c12b80e56d884"] 398 | entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] 399 | fastapi = ["7fc78704e65e0936309ecf05f5898b49eecddd65805c3703b895019d48add53b", "aabdafad6368146df507354c6f07d9b5521af5685e31d79036225fceaba253ca"] 400 | flake8 = ["19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", "8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"] 401 | future = ["67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"] 402 | h11 = ["acca6a44cb52a32ab442b1779adf0875c443c689e9e028f8d831a3769f9c5208", "f2b1ca39bfed357d1f19ac732913d5f9faa54a5062eca7d2ec3a916cfb7ae4c7"] 403 | httptools = ["e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc"] 404 | identify = ["4f1fe9a59df4e80fcb0213086fcf502bc1765a01ea4fe8be48da3b65afd2a017", "d8919589bd2a5f99c66302fec0ef9027b12ae150b0b0213999ad3f695fc7296e"] 405 | idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"] 406 | importlib-metadata = ["23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", "80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3"] 407 | mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] 408 | more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"] 409 | nodeenv = ["ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a"] 410 | pre-commit = ["1d3c0587bda7c4e537a46c27f2c84aa006acc18facf9970bf947df596ce91f3f", "fa78ff96e8e9ac94c748388597693f18b041a181c94a4f039ad20f45287ba44a"] 411 | pyasn1 = ["0c444a3482c5f5c7fab93567761324f77045ede002362171e12acdd400ea50e0", "27e919f274d96829d9c78455eacf6a2253c9fd44979e5f880b672b524161366d", "3bb81821d47b17146049e7574ab4bf1e315eb7aead30efe5d6a9ca422c9710be", "3f8b11ba9fde9aeb56882589896cf9c7c8f4d5630f5e83abec1d80d1ef37b28b", "40f307cb9e351bf54b5cf956a85e02a42d4f881dac79ce7d0b736acb2adab0e5", "54734028b18e1d625a788d9846479ce088f10015db9ffb1abdd406d82b68b600", "5616c045d1eb934fecc0162bc2b9bd2c8935d4a3c4642c3ccd96fb1528b1f218", "5eb6dbc1191dc8a18da9d3ee4c3133909e3cfd0967d434dee958e737c1ca0bb7", "72f5f934852f4722e769ec9a4dd20d6fa206a78186bab2aadf27753a222892f6", "86ddc0f9a9062f111e70de780c5eb6d5d726f44809fafaa0af7a534ed66fc7c1", "b17f6696f920dc712a4dc5c711b1abd623d80531910e1455c70a6cb85ffb6332", "b773d5c9196ffbc3a1e13bdf909d446cad80a039aa3340bcad72f395b76ebc86", "f8dda822e63df09237acd8f88940c68c1964076e5d9a906cbf385d71ec1a4006"] 412 | pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"] 413 | pydantic = ["0b7998c6e4106e57317a511a17ec8b3c99a20f037daa9687e2cffb48f20e36d3", "239a05529f01ff096e0b4b3ae8b0fd891dc1571ddca6fe4c013ed1c46646d8bf", "2408cbfd8aadf61a5a8062734615dc5f54bc01fcb37ad476260d0c8288915607", "427c0935683913a45ee5225ce4cd2ebbde376b7b06d4287f7f6c6d3158ef0408", "5b8900778c30e55ee418f6a375b922873d806232021ac0be13d0123fd922557b", "cb4351bae00636b59cdaafe75013955c3002f842878b93625f7a68202c8d218a"] 414 | pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] 415 | python-dotenv = ["debd928b49dbc2bf68040566f55cdb3252458036464806f4094487244e2a4093", "f157d71d5fec9d4bd5f51c82746b6344dffa680ee85217c123f4a0c8117c4544"] 416 | python-jose = ["29701d998fe560e52f17246c3213a882a4a39da7e42c7015bcc1f7823ceaff1c", "ed7387f0f9af2ea0ddc441d83a6eb47a5909bd0c8a72ac3250e75afec2cc1371"] 417 | pyyaml = ["0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"] 418 | requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"] 419 | rsa = ["14ba45700ff1ec9eeb206a2ce76b32814958a98e372006c8fb76ba820211be66", "1a836406405730121ae9823e19c6e806c62bbad73f890574fff50efa4122c487"] 420 | six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] 421 | starlette = ["849553e6dc8dd4faac5d4383219bdf49b199f3f09d60ce374c2a00a42ab7c77d"] 422 | toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] 423 | urllib3 = ["b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", "dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"] 424 | uvicorn = ["8aa44f9d9c3082ef693950387ea25d376e32944df6d4071dbd8edc3c25a40c74"] 425 | uvloop = ["0fcd894f6fc3226a962ee7ad895c4f52e3f5c3c55098e21efb17c071849a0573", "2f31de1742c059c96cb76b91c5275b22b22b965c886ee1fced093fa27dde9e64", "459e4649fcd5ff719523de33964aa284898e55df62761e7773d088823ccbd3e0", "67867aafd6e0bc2c30a079603a85d83b94f23c5593b3cc08ec7e58ac18bf48e5", "8c200457e6847f28d8bb91c5e5039d301716f5f2fce25646f5fb3fd65eda4a26", "958906b9ca39eb158414fbb7d6b8ef1b7aee4db5c8e8e5d00fcbb69a1ce9dca7", "ac1dca3d8f3ef52806059e81042ee397ac939e5a86c8a3cea55d6b087db66115", "b284c22d8938866318e3b9d178142b8be316c52d16fcfe1560685a686718a021", "c48692bf4587ce281d641087658eca275a5ad3b63c78297bbded96570ae9ce8f", "fefc3b2b947c99737c348887db2c32e539160dcbeb7af9aa6b53db7a283538fe"] 426 | virtualenv = ["94a6898293d07f84a98add34c4df900f8ec64a570292279f6d91c781d37fd305", "f6fc312c031f2d2344f885de114f1cb029dfcffd26aa6e57d2ee2296935c4e7d"] 427 | websockets = ["04b42a1b57096ffa5627d6a78ea1ff7fad3bc2c0331ffc17bc32a4024da7fea0", "08e3c3e0535befa4f0c4443824496c03ecc25062debbcf895874f8a0b4c97c9f", "10d89d4326045bf5e15e83e9867c85d686b612822e4d8f149cf4840aab5f46e0", "232fac8a1978fc1dead4b1c2fa27c7756750fb393eb4ac52f6bc87ba7242b2fa", "4bf4c8097440eff22bc78ec76fe2a865a6e658b6977a504679aaf08f02c121da", "51642ea3a00772d1e48fb0c492f0d3ae3b6474f34d20eca005a83f8c9c06c561", "55d86102282a636e195dad68aaaf85b81d0bef449d7e2ef2ff79ac450bb25d53", "564d2675682bd497b59907d2205031acbf7d3fadf8c763b689b9ede20300b215", "5d13bf5197a92149dc0badcc2b699267ff65a867029f465accfca8abab95f412", "5eda665f6789edb9b57b57a159b9c55482cbe5b046d7db458948370554b16439", "5edb2524d4032be4564c65dc4f9d01e79fe8fad5f966e5b552f4e5164fef0885", "79691794288bc51e2a3b8de2bc0272ca8355d0b8503077ea57c0716e840ebaef", "7fcc8681e9981b9b511cdee7c580d5b005f3bb86b65bde2188e04a29f1d63317", "8e447e05ec88b1b408a4c9cde85aa6f4b04f06aa874b9f0b8e8319faf51b1fee", "90ea6b3e7787620bb295a4ae050d2811c807d65b1486749414f78cfd6fb61489", "9e13239952694b8b831088431d15f771beace10edfcf9ef230cefea14f18508f", "d40f081187f7b54d7a99d8a5c782eaa4edc335a057aa54c85059272ed826dc09", "e1df1a58ed2468c7b7ce9a2f9752a32ad08eac2bcd56318625c3647c2cd2da6f", "e98d0cec437097f09c7834a11c69d79fe6241729b23f656cfc227e93294fc242", "f8d59627702d2ff27cb495ca1abdea8bd8d581de425c56e93bff6517134e0a9b", "fc30cdf2e949a2225b012a7911d1d031df3d23e99b7eda7dfc982dc4a860dae9"] 428 | zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"] 429 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "fastapi_jwt" 3 | version = "0.1.0" 4 | description = "Example project to integrate JWT authentication into FastAPI." 5 | authors = ["Johannes Gontrum "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.7" 10 | fastapi = "^0.36.0" 11 | requests = "^2.22" 12 | python-jose = "^3.0" 13 | python-dotenv = "^0.10.3" 14 | uvicorn = "^0.8.6" 15 | 16 | [tool.poetry.dev-dependencies] 17 | black = {version = "^18.3-alpha.4",allows-prereleases = true} 18 | pre-commit = "^1.17" 19 | flake8 = "^3.7" 20 | 21 | [tool.black] 22 | line-length = 90 23 | target_version = ["py37"] 24 | exclude = "\\.git" 25 | 26 | [build-system] 27 | requires = ["poetry>=0.12"] 28 | build-backend = "poetry.masonry.api" 29 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | poetry install 4 | poetry run pre-commit install 5 | 6 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | poetry run uvicorn app.service:app --reload 4 | 5 | --------------------------------------------------------------------------------