├── .gitignore ├── 00-Starter-Seed ├── Procfile ├── .gitignore ├── .env.example ├── requirements.txt ├── .dockerignore ├── exec.ps1 ├── exec.sh ├── Dockerfile ├── README.md └── server.py ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── Feature Request.yml │ └── Bug Report.yml ├── dependabot.yml ├── workflows │ └── semgrep.yml └── stale.yml ├── LICENSE ├── README.md └── .circleci └── config.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /00-Starter-Seed/Procfile: -------------------------------------------------------------------------------- 1 | web: python server.py -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @auth0-samples/dx-sdks-engineer 2 | -------------------------------------------------------------------------------- /00-Starter-Seed/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | *.iml 3 | .idea 4 | -------------------------------------------------------------------------------- /00-Starter-Seed/.env.example: -------------------------------------------------------------------------------- 1 | AUTH0_DOMAIN={DOMAIN} 2 | API_IDENTIFIER={API_IDENTIFIER} 3 | -------------------------------------------------------------------------------- /00-Starter-Seed/requirements.txt: -------------------------------------------------------------------------------- 1 | flask==2.3.3 2 | python-dotenv 3 | pyjwt 4 | flask-cors 5 | six -------------------------------------------------------------------------------- /00-Starter-Seed/.dockerignore: -------------------------------------------------------------------------------- 1 | .env 2 | .env.example 3 | .gitignore 4 | README.md 5 | exec.sh 6 | exec.ps1 7 | Procfile -------------------------------------------------------------------------------- /00-Starter-Seed/exec.ps1: -------------------------------------------------------------------------------- 1 | docker build -t auth0-python-api . 2 | docker run --env-file .env -p 3010:3010 -it auth0-python-api 3 | -------------------------------------------------------------------------------- /00-Starter-Seed/exec.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | docker build -t auth0-python-api . 3 | docker run --env-file .env -p 3010:3010 -it auth0-python-api 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🤔 Help & Questions 4 | url: https://community.auth0.com 5 | about: Ask general support or usage questions in the Auth0 Community forums. 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/00-Starter-Seed" 5 | schedule: 6 | interval: "daily" 7 | ignore: 8 | - dependency-name: "*" 9 | update-types: ["version-update:semver-major", "version-update:semver-patch"] 10 | -------------------------------------------------------------------------------- /00-Starter-Seed/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | WORKDIR /home/app 4 | 5 | #If we add the requirements and install dependencies first, docker can use cache if requirements don't change 6 | ADD requirements.txt /home/app 7 | RUN pip install -r requirements.txt 8 | 9 | ADD . /home/app 10 | CMD python server.py 11 | 12 | EXPOSE 3010 -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | name: Semgrep 2 | 3 | on: 4 | pull_request: {} 5 | 6 | push: 7 | branches: ["master", "main"] 8 | 9 | schedule: 10 | - cron: '30 0 1,15 * *' 11 | 12 | jobs: 13 | semgrep: 14 | name: Scan 15 | runs-on: ubuntu-latest 16 | container: 17 | image: returntocorp/semgrep 18 | # Skip any PR created by dependabot to avoid permission issues 19 | if: (github.actor != 'dependabot[bot]') 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - run: semgrep ci 24 | env: 25 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 90 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | daysUntilClose: 7 8 | 9 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 10 | exemptLabels: [] 11 | 12 | # Set to true to ignore issues with an assignee (defaults to false) 13 | exemptAssignees: true 14 | 15 | # Label to use when marking as stale 16 | staleLabel: closed:stale 17 | 18 | # Comment to post when marking as stale. Set to `false` to disable 19 | markComment: > 20 | This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Auth0 Samples 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: -------------------------------------------------------------------------------- 1 | # Auth0 + Python Api 2 | 3 | [![CircleCI](https://circleci.com/gh/auth0-samples/auth0-python-api-samples.svg?style=svg)](https://circleci.com/gh/auth0-samples/auth0-python-api-samples) 4 | 5 | 6 | This repository contains the source code for the [Python API Quickstart](https://auth0.com/docs/quickstart/backend/python). 7 | 8 | ## What is Auth0? 9 | 10 | Auth0 helps you to easily: 11 | 12 | - implement authentication with multiple identity providers, including social (e.g., Google, Facebook, Microsoft, LinkedIn, GitHub, Twitter, etc), or enterprise (e.g., Windows Azure AD, Google Apps, Active Directory, ADFS, SAML, etc.) 13 | - log in users with username/password databases, passwordless, or multi-factor authentication 14 | - link multiple user accounts together 15 | - generate signed JSON Web Tokens to authorize your API calls and flow the user identity securely 16 | - access demographics and analytics detailing how, when, and where users are logging in 17 | - enrich user profiles from other data sources using customizable JavaScript rules 18 | 19 | [Why Auth0?](https://auth0.com/why-auth0) 20 | 21 | ## Create a free account in Auth0 22 | 23 | 1. Go to [Auth0](https://auth0.com) and click Sign Up. 24 | 2. Use Google, GitHub or Microsoft Account to login. 25 | 26 | ## Issue Reporting 27 | 28 | If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. 29 | 30 | ## License 31 | 32 | This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature Request.yml: -------------------------------------------------------------------------------- 1 | name: 🧩 Feature request 2 | description: Suggest an idea or a feature for this sample 3 | labels: ["feature request"] 4 | 5 | body: 6 | - type: checkboxes 7 | id: checklist 8 | attributes: 9 | label: Checklist 10 | options: 11 | - label: I have looked into the [Readme](https://github.com/auth0-samples/auth0-python-api-samples/tree/master/00-Starter-Seed#readme) and have not found a suitable solution or answer. 12 | required: true 13 | - label: I have searched the [issues](https://github.com/auth0-samples/auth0-python-api-samples/issues) and have not found a suitable solution or answer. 14 | required: true 15 | - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. 16 | required: true 17 | - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). 18 | required: true 19 | 20 | - type: textarea 21 | id: description 22 | attributes: 23 | label: Describe the problem you'd like to have solved 24 | description: A clear and concise description of what the problem is. 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | id: ideal-solution 30 | attributes: 31 | label: Describe the ideal solution 32 | description: A clear and concise description of what you want to happen. 33 | validations: 34 | required: true 35 | 36 | - type: textarea 37 | id: alternatives-and-workarounds 38 | attributes: 39 | label: Alternatives and current workarounds 40 | description: A clear and concise description of any alternatives you've considered or any workarounds that are currently in place. 41 | validations: 42 | required: false 43 | 44 | - type: textarea 45 | id: additional-context 46 | attributes: 47 | label: Additional context 48 | description: Add any other context or screenshots about the feature request here. 49 | validations: 50 | required: false 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug Report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Report a bug 2 | description: Have you found a bug or issue? Create a bug report for this sample 3 | 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | **Please do not report security vulnerabilities here**. The [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues. 9 | 10 | - type: checkboxes 11 | id: checklist 12 | attributes: 13 | label: Checklist 14 | options: 15 | - label: I have looked into the [Readme](https://github.com/auth0-samples/auth0-python-api-samples/tree/master/00-Starter-Seed#readme) and have not found a suitable solution or answer. 16 | required: true 17 | - label: I have searched the [issues](https://github.com/auth0-samples/auth0-python-api-samples/issues) and have not found a suitable solution or answer. 18 | required: true 19 | - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. 20 | required: true 21 | - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). 22 | required: true 23 | 24 | - type: textarea 25 | id: description 26 | attributes: 27 | label: Description 28 | description: Provide a clear and concise description of the issue, including what you expected to happen. 29 | validations: 30 | required: true 31 | 32 | - type: textarea 33 | id: reproduction 34 | attributes: 35 | label: Reproduction 36 | description: Detail the steps taken to reproduce this error, and whether this issue can be reproduced consistently or if it is intermittent. 37 | placeholder: | 38 | 1. Step 1... 39 | 2. Step 2... 40 | 3. ... 41 | validations: 42 | required: true 43 | 44 | - type: textarea 45 | id: additional-context 46 | attributes: 47 | label: Additional context 48 | description: Any other relevant information you think would be useful. 49 | validations: 50 | required: false 51 | -------------------------------------------------------------------------------- /00-Starter-Seed/README.md: -------------------------------------------------------------------------------- 1 | # Auth0 + Python + Flask API Seed 2 | 3 | This is the seed project you need to use if you're going to create a Python + Flask API. 4 | If you just want to create a Regular Python WebApp, please 5 | check [this project](https://github.com/auth0-samples/auth0-python-web-app/tree/master/01-Login) 6 | 7 | Please check our [Quickstart](https://auth0.com/docs/quickstart/backend/python) to better understand this sample. 8 | 9 | # Running the example 10 | 11 | In order to run the example you need to have `python` and `pip` installed. 12 | 13 | You also need to set your Auth0 Domain and the API's audience as environment variables with the following names 14 | respectively: `AUTH0_DOMAIN` and `API_IDENTIFIER`, which is the audience of your API. You can find an example in the 15 | `env.example` file. 16 | 17 | For that, if you just create a file named `.env` in the directory and set the values like the following, 18 | the app will just work: 19 | 20 | ```bash 21 | # .env file 22 | AUTH0_DOMAIN=example.auth0.com 23 | API_IDENTIFIER={API_IDENTIFIER} 24 | ``` 25 | 26 | Once you've set those 2 environment variables: 27 | 28 | 1. Install the needed dependencies with `pip install -r requirements.txt` 29 | 2. Start the server with `python server.py` 30 | 3. Try calling [http://localhost:3010/api/public](http://localhost:3010/api/public) 31 | 32 | # Testing the API 33 | 34 | You can then try to do a GET to [http://localhost:3010/api/private](http://localhost:3010/api/private) which will 35 | throw an error if you don't send an access token signed with RS256 with the appropriate issuer and audience in the 36 | Authorization header. 37 | 38 | You can also try to do a GET to 39 | [http://localhost:3010/api/private-scoped](http://localhost:3010/api/private-scoped) which will throw an error if 40 | you don't send an access token with the scope `read:messages` signed with RS256 with the appropriate issuer and audience 41 | in the Authorization header. 42 | 43 | # Running the example with Docker 44 | 45 | In order to run the sample with [Docker](https://www.docker.com/) you need to add the `AUTH0_DOMAIN` and `API_ID` 46 | to the `.env` filed as explained [previously](#running-the-example) and then 47 | 48 | 1. Execute in command line `sh exec.sh` to run the Docker in Linux, or `.\exec.ps1` to run the Docker in Windows. 49 | 2. Try calling [http://localhost:3010/api/public](http://localhost:3010/api/public) -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Common Logic 2 | machine: &machine-cfg 3 | image: ubuntu-2004:202107-02 4 | 5 | defaults: &defaults 6 | steps: 7 | - attach_workspace: 8 | at: ~/ 9 | - run: 10 | name: Prepare environment variables 11 | command: | 12 | cd $AUTH0_CFG 13 | mv .env.example .env 14 | sed -i 's|{DOMAIN}|'$auth0_domain'|g' .env 15 | sed -i 's|{API_IDENTIFIER}|'$api_identifier'|g' .env 16 | - run: 17 | name: Background Server 18 | command: cd $AUTH0_CFG && sh exec.sh 19 | background: true 20 | - run: 21 | name: Wait until server is online 22 | command: | 23 | until $(curl --silent --head --output /dev/null --fail http://localhost:3010/api/public); do 24 | sleep 5 25 | done 26 | - run: 27 | name: Prepare tests 28 | command: | 29 | cd test 30 | echo "AUTH0_DOMAIN=$auth0_domain" >> .env 31 | echo "API_IDENTIFIER=$api_identifier" >> .env 32 | echo "AUTH0_CLIENT_ID_1=$client_id_scopes_none" >> .env 33 | echo "AUTH0_CLIENT_SECRET_1=$client_secret_scopes_none" >> .env 34 | echo "AUTH0_CLIENT_ID_2=$client_id_scopes_read" >> .env 35 | echo "AUTH0_CLIENT_SECRET_2=$client_secret_scopes_read" >> .env 36 | echo "AUTH0_CLIENT_ID_3=$client_id_scopes_write" >> .env 37 | echo "AUTH0_CLIENT_SECRET_3=$client_secret_scopes_write" >> .env 38 | echo "AUTH0_CLIENT_ID_4=$client_id_scopes_readwrite" >> .env 39 | echo "AUTH0_CLIENT_SECRET_4=$client_secret_scopes_readwrite" >> .env 40 | echo "API_URL=http://localhost:3010" >> .env 41 | npm install 42 | - run: 43 | name: Execute automated tests 44 | command: cd test && npm test 45 | 46 | # Jobs and Workflows 47 | version: 2 48 | jobs: 49 | checkout: 50 | machine: 51 | <<: *machine-cfg 52 | steps: 53 | - checkout 54 | - run: 55 | name: Clone test script 56 | command: git clone -b v0.0.1 --depth 1 https://github.com/auth0-samples/api-quickstarts-tests test 57 | - persist_to_workspace: 58 | root: ~/ 59 | paths: 60 | - project 61 | - test 62 | 00-Starter-Seed: 63 | machine: 64 | <<: *machine-cfg 65 | environment: 66 | AUTH0_CFG: 00-Starter-Seed 67 | SAMPLE_PATH: 00-Starter-Seed 68 | <<: *defaults 69 | workflows: 70 | version: 2 71 | API-Tests: 72 | jobs: 73 | - checkout: 74 | context: Quickstart API Tests 75 | - 00-Starter-Seed: 76 | context: Quickstart API Tests 77 | requires: 78 | - checkout 79 | -------------------------------------------------------------------------------- /00-Starter-Seed/server.py: -------------------------------------------------------------------------------- 1 | """Python Flask API Auth0 integration example 2 | """ 3 | 4 | from functools import wraps 5 | import json 6 | from os import environ as env 7 | from typing import Dict 8 | 9 | from six.moves.urllib.request import urlopen 10 | 11 | from dotenv import load_dotenv, find_dotenv 12 | from flask import Flask, request, jsonify, _request_ctx_stack, Response 13 | from flask_cors import cross_origin 14 | import jwt 15 | 16 | ENV_FILE = find_dotenv() 17 | if ENV_FILE: 18 | load_dotenv(ENV_FILE) 19 | AUTH0_DOMAIN = env.get("AUTH0_DOMAIN") 20 | API_IDENTIFIER = env.get("API_IDENTIFIER") 21 | ALGORITHMS = ["RS256"] 22 | APP = Flask(__name__) 23 | 24 | 25 | # Format error response and append status code. 26 | class AuthError(Exception): 27 | """ 28 | An AuthError is raised whenever the authentication failed. 29 | """ 30 | def __init__(self, error: Dict[str, str], status_code: int): 31 | super().__init__() 32 | self.error = error 33 | self.status_code = status_code 34 | 35 | 36 | @APP.errorhandler(AuthError) 37 | def handle_auth_error(ex: AuthError) -> Response: 38 | """ 39 | serializes the given AuthError as json and sets the response status code accordingly. 40 | :param ex: an auth error 41 | :return: json serialized ex response 42 | """ 43 | response = jsonify(ex.error) 44 | response.status_code = ex.status_code 45 | return response 46 | 47 | 48 | def get_token_auth_header() -> str: 49 | """Obtains the access token from the Authorization Header 50 | """ 51 | auth = request.headers.get("Authorization", None) 52 | if not auth: 53 | raise AuthError({"code": "authorization_header_missing", 54 | "description": 55 | "Authorization header is expected"}, 401) 56 | 57 | parts = auth.split() 58 | 59 | if parts[0].lower() != "bearer": 60 | raise AuthError({"code": "invalid_header", 61 | "description": 62 | "Authorization header must start with" 63 | " Bearer"}, 401) 64 | if len(parts) == 1: 65 | raise AuthError({"code": "invalid_header", 66 | "description": "Token not found"}, 401) 67 | if len(parts) > 2: 68 | raise AuthError({"code": "invalid_header", 69 | "description": 70 | "Authorization header must be" 71 | " Bearer token"}, 401) 72 | 73 | token = parts[1] 74 | return token 75 | 76 | 77 | def requires_scope(required_scope: str) -> bool: 78 | """Determines if the required scope is present in the access token 79 | Args: 80 | required_scope (str): The scope required to access the resource 81 | """ 82 | token = get_token_auth_header() 83 | unverified_claims = jwt.decode(token, options={"verify_signature": False}) 84 | if unverified_claims.get("scope"): 85 | token_scopes = unverified_claims["scope"].split() 86 | for token_scope in token_scopes: 87 | if token_scope == required_scope: 88 | return True 89 | return False 90 | 91 | 92 | def requires_auth(func): 93 | """Determines if the access token is valid 94 | """ 95 | 96 | @wraps(func) 97 | def decorated(*args, **kwargs): 98 | token = get_token_auth_header() 99 | jsonurl = urlopen("https://" + AUTH0_DOMAIN + "/.well-known/jwks.json") 100 | jwks = json.loads(jsonurl.read()) 101 | try: 102 | unverified_header = jwt.get_unverified_header(token) 103 | except jwt.PyJWTError as jwt_error: 104 | raise AuthError({"code": "invalid_header", 105 | "description": 106 | "Invalid header. " 107 | "Use an RS256 signed JWT Access Token"}, 401) from jwt_error 108 | if unverified_header["alg"] == "HS256": 109 | raise AuthError({"code": "invalid_header", 110 | "description": 111 | "Invalid header. " 112 | "Use an RS256 signed JWT Access Token"}, 401) 113 | public_key = None 114 | for jwk in jwks["keys"]: 115 | if jwk["kid"] == unverified_header["kid"]: 116 | public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk)) 117 | if public_key: 118 | try: 119 | payload = jwt.decode( 120 | token, 121 | public_key, 122 | algorithms=ALGORITHMS, 123 | audience=API_IDENTIFIER, 124 | issuer="https://" + AUTH0_DOMAIN + "/" 125 | ) 126 | except jwt.ExpiredSignatureError as expired_sign_error: 127 | raise AuthError({"code": "token_expired", 128 | "description": "token is expired"}, 401) from expired_sign_error 129 | except jwt.InvalidAudienceError as jwt_audience_error: 130 | raise AuthError({"code": "invalid_audience", 131 | "description": 132 | "incorrect audience," 133 | " please check the audience"}, 401) from jwt_audience_error 134 | except jwt.InvalidIssuerError as jwt_issuer_error: 135 | raise AuthError({"code": "invalid_issuer", 136 | "description": 137 | "incorrect issuer," 138 | " please check the issuer"}, 401) from jwt_issuer_error 139 | except Exception as exc: 140 | raise AuthError({"code": "invalid_header", 141 | "description": 142 | "Unable to parse authentication" 143 | " token."}, 401) from exc 144 | 145 | _request_ctx_stack.top.current_user = payload 146 | return func(*args, **kwargs) 147 | raise AuthError({"code": "invalid_header", 148 | "description": "Unable to find appropriate key"}, 401) 149 | 150 | return decorated 151 | 152 | 153 | # Controllers API 154 | @APP.route("/api/public") 155 | @cross_origin(headers=["Content-Type", "Authorization"]) 156 | def public(): 157 | """No access token required to access this route 158 | """ 159 | response = "Hello from a public endpoint! You don't need to be authenticated to see this." 160 | return jsonify(message=response) 161 | 162 | 163 | @APP.route("/api/private") 164 | @cross_origin(headers=["Content-Type", "Authorization"]) 165 | @cross_origin(headers=["Access-Control-Allow-Origin", "http://localhost:3000"]) 166 | @requires_auth 167 | def private(): 168 | """A valid access token is required to access this route 169 | """ 170 | response = "Hello from a private endpoint! You need to be authenticated to see this." 171 | return jsonify(message=response) 172 | 173 | 174 | @APP.route("/api/private-scoped") 175 | @cross_origin(headers=["Content-Type", "Authorization"]) 176 | @cross_origin(headers=["Access-Control-Allow-Origin", "http://localhost:3000"]) 177 | @requires_auth 178 | def private_scoped(): 179 | """A valid access token and an appropriate scope are required to access this route 180 | """ 181 | if requires_scope("read:messages"): 182 | response = "Hello from a private endpoint! You need to be authenticated and have a scope of read:messages to see this." 183 | return jsonify(message=response) 184 | raise AuthError({ 185 | "code": "Unauthorized", 186 | "description": "You don't have access to this resource" 187 | }, 403) 188 | 189 | 190 | if __name__ == "__main__": 191 | APP.run(host="0.0.0.0", port=env.get("PORT", 3010)) 192 | --------------------------------------------------------------------------------