{"id":21951583,"url":"https://github.com/toxe/python-flask-rest-jwt","last_synced_at":"2025-07-04T00:05:40.672Z","repository":{"id":134564687,"uuid":"264156733","full_name":"Toxe/python-flask-rest-jwt","owner":"Toxe","description":"A (very) basic Flask REST API example using JWT Authentication","archived":false,"fork":false,"pushed_at":"2020-09-25T10:02:00.000Z","size":60,"stargazers_count":11,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-29T00:51:16.651Z","etag":null,"topics":["api","flask","jwt","python","python3","rest"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Toxe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-05-15T09:48:44.000Z","updated_at":"2025-03-04T03:01:14.000Z","dependencies_parsed_at":"2023-06-18T22:53:42.241Z","dependency_job_id":null,"html_url":"https://github.com/Toxe/python-flask-rest-jwt","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Toxe/python-flask-rest-jwt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toxe%2Fpython-flask-rest-jwt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toxe%2Fpython-flask-rest-jwt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toxe%2Fpython-flask-rest-jwt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toxe%2Fpython-flask-rest-jwt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Toxe","download_url":"https://codeload.github.com/Toxe/python-flask-rest-jwt/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Toxe%2Fpython-flask-rest-jwt/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263421921,"owners_count":23464048,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","flask","jwt","python","python3","rest"],"created_at":"2024-11-29T06:15:07.574Z","updated_at":"2025-07-04T00:05:40.577Z","avatar_url":"https://github.com/Toxe.png","language":"Python","readme":"# A (very) basic Flask REST API example using JWT Authentication\n\nThis is a simple Python REST API server using Flask and JWT (JSON Web Tokens). It **does not** use a database or other persistent storage, instead it reads its data on startup from `data.json` and provides some simple database functions for data manipulation and queries. All changes are lost on server shutdown.\n\nThe JWT authentication supports access and refresh tokens and token revoking by using an in-memory blacklist.\n\n## Dependencies\n\n- Python 3\n- [Poetry](https://python-poetry.org)\n\n## Setup Virtual Environment and install Dependencies\n\n```\n$ poetry install\n$ poetry shell\n```\n\n## Configuration\n\n### Flask\n\n#### `.flaskenv` for development and debugging\n\n```ini\nFLASK_APP=main\nFLASK_ENV=development\n```\n\n### Visual Studio Code\n\n#### `.vscode/settings.json`\n\n```json\n{\n    \"python.linting.pylintArgs\": [\n        \"--load-plugins\",\n        \"pylint-flask\"\n    ]\n}\n```\n\n## Example data\n\nExample user and ship data is stored in `data.json`.\n\n## Routes\n\n```\n$ flask routes --sort rule\nEndpoint                   Methods  Rule\n-------------------------  -------  -----------------------\napi.get_ships              GET      /api/ships\napi.create_ship            POST     /api/ships\napi.get_ship               GET      /api/ships/\u003cint:id\u003e\napi.update_ship            PUT      /api/ships/\u003cint:id\u003e\napi.delete_ship            DELETE   /api/ships/\u003cint:id\u003e\napi.get_users              GET      /api/users\napi.create_user            POST     /api/users\napi.get_user               GET      /api/users/\u003cint:id\u003e\napi.update_user            PUT      /api/users/\u003cint:id\u003e\napi.delete_user            DELETE   /api/users/\u003cint:id\u003e\nauth.login                 POST     /auth/login\nauth.logout_access_token   DELETE   /auth/logout\nauth.logout_refresh_token  DELETE   /auth/logout2\nauth.refresh               POST     /auth/refresh\nstatic                     GET      /static/\u003cpath:filename\u003e\n```\n\n## Running (development version)\n\n```\nflask run\n```\n\n## Tests\n\nRun either of:\n\n```\npytest\n```\n\n```\npython3 -m pytest\n```\n\n## REST API\n\n### Authentication\n\n##### `POST` `/auth/login`: Login\n\n```\n$ curl -i http://localhost:5000/auth/login -X POST -d '{\"username\":\"user\", \"password\":\"password\"}' -H \"Content-Type: application/json\"\n```\n\n```\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 568\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Fri, 15 May 2020 12:56:25 GMT\n\n{\n  \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODk1NDczODUsIm5iZiI6MTU4OTU0NzM4NSwianRpIjoiMDE5NmJkMjAtZGMxOC00NTI0LWEzM2UtNWEzYTZiNGMxZTQ2IiwiZXhwIjoxNTg5NTQ4Mjg1LCJpZGVudGl0eSI6MSwiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.7Jeg7_Yv22vzKAM6ZfOgp5JQjEGAJLWB_k6qDpfx5HU\",\n  \"refresh_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODk1NDczODUsIm5iZiI6MTU4OTU0NzM4NSwianRpIjoiMmFhZDQxNTEtMDFmNi00YzM4LWFiYjctZWE2M2YyYjhlMjE3IiwiZXhwIjoxNTkyMTM5Mzg1LCJpZGVudGl0eSI6MSwidHlwZSI6InJlZnJlc2gifQ.SazEtt-odtprlo2eD8XiE90chQm87PfYt7UyMj5rvVk\"\n}\n```\n\n##### `POST` `/auth/refresh`: Refresh access token\n\n```\n$ curl -i http://localhost:5000/auth/refresh -X POST -H \"Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODk1NDczODUsIm5iZiI6MTU4OTU0NzM4NSwianRpIjoiMmFhZDQxNTEtMDFmNi00YzM4LWFiYjctZWE2M2YyYjhlMjE3IiwiZXhwIjoxNTkyMTM5Mzg1LCJpZGVudGl0eSI6MSwidHlwZSI6InJlZnJlc2gifQ.SazEtt-odtprlo2eD8XiE90chQm87PfYt7UyMj5rvVk\"\n```\n\n```\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 293\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Fri, 15 May 2020 12:57:56 GMT\n\n{\n  \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODk1NDc0NzYsIm5iZiI6MTU4OTU0NzQ3NiwianRpIjoiNWE1Mzg0MGUtYjZmNS00ZTFkLTg3MGMtYzViNDliYmVkOGQzIiwiZXhwIjoxNTg5NTQ4Mzc2LCJpZGVudGl0eSI6MSwiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.4j4jonxmnAP1hFkJSCryIkKWOrMtJU02BqUIBUukpKA\"\n}\n```\n\n##### `DELETE` `/auth/logout`: Logout and revoke access token\n\n```\n$ curl -i http://localhost:5000/auth/logout -X DELETE -H \"Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODk2MTc4NzIsIm5iZiI6MTU4OTYxNzg3MiwianRpIjoiYzUwNDlkMmEtZWIyZS00M2I1LWIzNjgtMTJjYWFiMjA3ZTJjIiwiZXhwIjoxNTg5NjE4NzcyLCJpZGVudGl0eSI6MSwiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.XoQtFc-a9ak4ON8TQ6rKYzz8IZWlYCeTZph4fomX-tw\"\n```\n\n```\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 44\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Sat, 16 May 2020 08:32:18 GMT\n\n{\n  \"message\": \"Successfully logged out.\"\n}\n```\n\n##### `DELETE` `/auth/logout2`: Logout and revoke refresh token\n\n```\n$ curl -i http://localhost:5000/auth/logout2 -X DELETE -H \"Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODk2MTc4NzIsIm5iZiI6MTU4OTYxNzg3MiwianRpIjoiYzA2YTA2NWEtMmJiYS00ZTJlLTliZTctY2Y0YWM4MWUwZDA2IiwiZXhwIjoxNTkyMjA5ODcyLCJpZGVudGl0eSI6MSwidHlwZSI6InJlZnJlc2gifQ.XsZ1cHcFhA60k4z87-bbuHBkRmWD6hKAXifzFq2NjOw\"\n```\n\n```\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 44\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Sat, 16 May 2020 08:34:18 GMT\n\n{\n  \"message\": \"Successfully logged out.\"\n}\n```\n\n### Users\n\n##### `GET` `/api/users`: List all users\n\nThis will not return stored passwords.\n\n```\n$ curl -i http://localhost:5000/api/users\n```\n\n```\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 89\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Fri, 15 May 2020 12:58:30 GMT\n\n[\n  {\n    \"id\": 1,\n    \"name\": \"user\"\n  },\n  {\n    \"id\": 2,\n    \"name\": \"guest\"\n  }\n]\n```\n\n##### `GET` `/api/users/\u003cid\u003e`: Query single user\n\nThis will not return the user password.\n\n```\n$ curl -i http://localhost:5000/api/users/1\n```\n\n```\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 33\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Fri, 15 May 2020 12:58:52 GMT\n\n{\n  \"id\": 1,\n  \"name\": \"user\"\n}\n```\n\n##### `POST` `/api/users`: Create new user\n\n```\n$ curl -i http://localhost:5000/api/users -X POST -d '{\"name\":\"new user\", \"password\":\"secret\"}' -H \"Content-Type: application/json\"\n```\n\n```\nHTTP/1.0 201 CREATED\nContent-Type: application/json\nContent-Length: 37\nLocation: http://localhost:5000/api/users/3\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Fri, 15 May 2020 12:59:19 GMT\n\n{\n  \"id\": 3,\n  \"name\": \"new user\"\n}\n```\n\n##### `PUT` `/api/users/\u003cid\u003e`: Update user data\n\nLogin required and can only change own data.\n\n```\n$ curl -i http://localhost:5000/api/users/3 -X PUT -d '{\"name\":\"fancy new name\", \"password\":\"more secret\"}' -H \"Content-Type: application/json\" -H \"Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODk1NDc3MDYsIm5iZiI6MTU4OTU0NzcwNiwianRpIjoiOTJjYTdhMTItY2Q4Yi00ZWJjLThlMWEtMjU1N2EwMGYwY2Y0IiwiZXhwIjoxNTg5NTQ4NjA2LCJpZGVudGl0eSI6MywiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.FLFRjkTAzpeT-ZOUsZmWegy5cn-EHM8EeC3Tskuu4Uc\"\n```\n\n```\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 43\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Fri, 15 May 2020 13:02:09 GMT\n\n{\n  \"id\": 3,\n  \"name\": \"fancy new name\"\n}\n```\n\n##### `DELETE` `/api/users/\u003cid\u003e`: Delete user\n\nLogin required and can only delete the current user.\n\n```\n$ curl -i http://localhost:5000/api/users/3 -X DELETE -H \"Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODk1NDc3MDYsIm5iZiI6MTU4OTU0NzcwNiwianRpIjoiOTJjYTdhMTItY2Q4Yi00ZWJjLThlMWEtMjU1N2EwMGYwY2Y0IiwiZXhwIjoxNTg5NTQ4NjA2LCJpZGVudGl0eSI6MywiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.FLFRjkTAzpeT-ZOUsZmWegy5cn-EHM8EeC3Tskuu4Uc\"\n```\n\n```\nHTTP/1.0 204 NO CONTENT\nContent-Type: text/html; charset=utf-8\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Fri, 15 May 2020 13:02:30 GMT\n```\n\n### Ships\n\n##### `GET` `/api/ships`: List all ships\n\n```\n$ curl -i http://localhost:5000/api/ships\n```\n\n```\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 4984\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Fri, 15 May 2020 13:03:08 GMT\n\n[\n  {\n    \"affiliation\": \"Rebel Alliance\",\n    \"category\": \"Starfighters\",\n    \"crew\": 1,\n    \"id\": 1,\n    \"length\": 13,\n    \"manufacturer\": \"Incom Corporation\",\n    \"model\": \"T-65 X-Wing\",\n    \"roles\": [\n      \"Space Superiority Starfighter\",\n      \"Escort\"\n    ],\n    \"ship_class\": \"Starfighter\"\n  },\n  {\"id\": 2, ...},\n  {\"id\": 3, ...},\n  {\"id\": 4, ...},\n  {\"id\": 5, ...},\n  {\"id\": 6, ...},\n  {\"id\": 7, ...},\n  {\"id\": 8, ...},\n  {\"id\": 9, ...},\n  {\"id\": 10, ...},\n  {\"id\": 11, ...},\n  {\"id\": 12, ...},\n  {\"id\": 13, ...},\n  {\"id\": 14, ...},\n  {\"id\": 15, ...}\n]\n```\n\n##### `GET` `/api/ship/\u003cid\u003e`: Query single ship\n\n```\n$ curl -i http://localhost:5000/api/ships/2\n```\n\n```\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 267\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Fri, 15 May 2020 13:06:32 GMT\n\n{\n  \"affiliation\": \"Empire\",\n  \"category\": \"Starfighters\",\n  \"crew\": 1,\n  \"id\": 2,\n  \"length\": 7,\n  \"manufacturer\": \"Sienar Fleet Systems\",\n  \"model\": \"TIE/LN Starfighter\",\n  \"roles\": [\n    \"Space Superiority Starfighter\"\n  ],\n  \"ship_class\": \"Starfighter\"\n}\n```\n\n##### `POST` `/api/ship`: Create new ship\n\nLogin required.\n\n```\n$ curl -i http://localhost:5000/api/ships -X POST -d '{\"ship_class\":\"Star Destroyer\", \"model\":\"Imperial I-class Star Destroyer\", \"affiliation\":\"Empire\", \"category\":\"Capital Ships\", \"crew\":37085, \"length\":1600, \"manufacturer\":\"Kuat Drive Yards\", \"roles\":[\"Destroyer\",\"Carrier\",\"Military Transport\",\"Command Ship\"]}' -H \"Content-Type: application/json\" -H \"Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODk1NDc0NzYsIm5iZiI6MTU4OTU0NzQ3NiwianRpIjoiNWE1Mzg0MGUtYjZmNS00ZTFkLTg3MGMtYzViNDliYmVkOGQzIiwiZXhwIjoxNTg5NTQ4Mzc2LCJpZGVudGl0eSI6MSwiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.4j4jonxmnAP1hFkJSCryIkKWOrMtJU02BqUIBUukpKA\"\n```\n\n```\nHTTP/1.0 201 CREATED\nContent-Type: application/json\nContent-Length: 332\nLocation: http://localhost:5000/api/ships/16\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Fri, 15 May 2020 13:07:38 GMT\n\n{\n  \"affiliation\": \"Empire\",\n  \"category\": \"Capital Ships\",\n  \"crew\": 37085,\n  \"id\": 16,\n  \"length\": 1600,\n  \"manufacturer\": \"Kuat Drive Yards\",\n  \"model\": \"Imperial I-class Star Destroyer\",\n  \"roles\": [\n    \"Destroyer\",\n    \"Carrier\",\n    \"Military Transport\",\n    \"Command Ship\"\n  ],\n  \"ship_class\": \"Star Destroyer\"\n}\n```\n\n##### `PUT` `/api/ship/\u003cid\u003e`: Update ship\n\nLogin required.\n\n```\n$ curl -i http://localhost:5000/api/ships/16 -X PUT -d '{\"ship_class\":\"Star Destroyer\", \"model\":\"Imperial II-class Star Destroyer\", \"affiliation\":\"Empire\", \"category\":\"Capital Ships\", \"crew\":37050, \"length\":1600, \"manufacturer\":\"Kuat Drive Yards\", \"roles\":[\"Destroyer\",\"Carrier\",\"Military Transport\",\"Command Ship\"]}' -H \"Content-Type: application/json\" -H \"Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODk1NDc0NzYsIm5iZiI6MTU4OTU0NzQ3NiwianRpIjoiNWE1Mzg0MGUtYjZmNS00ZTFkLTg3MGMtYzViNDliYmVkOGQzIiwiZXhwIjoxNTg5NTQ4Mzc2LCJpZGVudGl0eSI6MSwiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.4j4jonxmnAP1hFkJSCryIkKWOrMtJU02BqUIBUukpKA\"\n```\n\n```\nHTTP/1.0 200 OK\nContent-Type: application/json\nContent-Length: 333\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Fri, 15 May 2020 13:07:56 GMT\n\n{\n  \"affiliation\": \"Empire\",\n  \"category\": \"Capital Ships\",\n  \"crew\": 37050,\n  \"id\": 16,\n  \"length\": 1600,\n  \"manufacturer\": \"Kuat Drive Yards\",\n  \"model\": \"Imperial II-class Star Destroyer\",\n  \"roles\": [\n    \"Destroyer\",\n    \"Carrier\",\n    \"Military Transport\",\n    \"Command Ship\"\n  ],\n  \"ship_class\": \"Star Destroyer\"\n}\n```\n\n##### `DELETE` `/api/ship/\u003cid\u003e`: Delete ship\n\nLogin required.\n\n```\n$ curl -i -i http://localhost:5000/api/ships/16 -X DELETE -H \"Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODk1NDc0NzYsIm5iZiI6MTU4OTU0NzQ3NiwianRpIjoiNWE1Mzg0MGUtYjZmNS00ZTFkLTg3MGMtYzViNDliYmVkOGQzIiwiZXhwIjoxNTg5NTQ4Mzc2LCJpZGVudGl0eSI6MSwiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.4j4jonxmnAP1hFkJSCryIkKWOrMtJU02BqUIBUukpKA\"\n```\n\n```\nHTTP/1.0 204 NO CONTENT\nContent-Type: text/html; charset=utf-8\nServer: Werkzeug/1.0.1 Python/3.8.2\nDate: Fri, 15 May 2020 13:08:25 GMT\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoxe%2Fpython-flask-rest-jwt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoxe%2Fpython-flask-rest-jwt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoxe%2Fpython-flask-rest-jwt/lists"}