diff --git a/main/docs.json b/main/docs.json index a694bc0457..44d5489042 100644 --- a/main/docs.json +++ b/main/docs.json @@ -2433,7 +2433,7 @@ "pages": [ "docs/quickstart/backend/nodejs/interactive", "docs/quickstart/backend/django/interactive", - "docs/quickstart/backend/python/interactive", + "docs/quickstart/backend/python/index", "docs/quickstart/backend/java-spring-security5/interactive", "docs/quickstart/backend/golang/interactive", "docs/quickstart/backend/aspnet-core-webapi", @@ -5068,7 +5068,7 @@ "pages": [ "docs/fr-ca/quickstart/backend/nodejs/interactive", "docs/fr-ca/quickstart/backend/django/interactive", - "docs/fr-ca/quickstart/backend/python/interactive", + "docs/fr-ca/quickstart/backend/python/index", "docs/fr-ca/quickstart/backend/java-spring-security5/interactive", "docs/fr-ca/quickstart/backend/golang/interactive", "docs/fr-ca/quickstart/backend/aspnet-core-webapi/interactive", @@ -7460,7 +7460,7 @@ "pages": [ "docs/ja-jp/quickstart/backend/nodejs/interactive", "docs/ja-jp/quickstart/backend/django/interactive", - "docs/ja-jp/quickstart/backend/python/interactive", + "docs/ja-jp/quickstart/backend/python/index", "docs/ja-jp/quickstart/backend/java-spring-security5/interactive", "docs/ja-jp/quickstart/backend/golang/interactive", "docs/ja-jp/quickstart/backend/aspnet-core-webapi/interactive", diff --git a/main/docs/quickstart/backend/python/_index.mdx b/main/docs/quickstart/backend/python/_index.mdx deleted file mode 100644 index 6aea771bd1..0000000000 --- a/main/docs/quickstart/backend/python/_index.mdx +++ /dev/null @@ -1,302 +0,0 @@ ---- -title: Add Authorization to Your Flask API Application -sidebarTitle: Python API - ---- -import { Recipe, Content, Section, SideMenu, SideMenuSectionItem, SignUpForm } from "/snippets/recipe.jsx"; -import { LoggedInForm } from "/snippets/Login.jsx"; -import Validator from "/snippets/quickstart/backend/python/validator.py.mdx"; -import Server from "/snippets/quickstart/backend/python/server.py.mdx"; - -import {AuthCodeGroup} from "/snippets/AuthCodeGroup.jsx"; - -export const sections = [ - { id: "define-permissions", title: "Define permissions" }, - { id: "install-dependencies", title: "Install dependencies" }, - { id: "create-the-jwt-validator", title: "Create the JWT validator" }, - { id: "create-a-flask-application", title: "Create a Flask application" } -] - - - - This guide demonstrates how to integrate Auth0 with any new or existing Python API built with [Flask](https://flask.palletsprojects.com/). - - If you haven't created an API in your Auth0 dashboard yet, you can use the interactive selector to create a new - Auth0 API or select an existing API that represents the project you want to integrate with. - - Alternatively, you can read [our getting started guide](https://auth0.com/docs/get-started/auth0-overview/set-up-apis) that helps you set up your first API through the Auth0 dashboard. - - Every API in Auth0 is configured using an API Identifier that your application code will use as the Audience to - validate the Access Token. - - - **New to Auth0?** Learn [how Auth0 works](https://auth0.com/docs/overview) - and read about [implementing API authentication - and authorization](https://auth0.com/docs/api-auth) using the OAuth 2.0 framework. - - -
- Permissions let you define how resources can be accessed on behalf of the user with a given access token. For - example, you might choose to grant read access to the `messages` resource if users have the manager - access level, and a write access to that resource if they have the administrator access level. - - You can define allowed permissions in the **Permissions** view of the Auth0 Dashboard's [APIs](https://manage.auth0.com/#/apis) section. - - - - - - - This example uses the `read:messages` scope. - - - -
- -
- Add the following dependencies to your `requirements.txt`: - - ```txt lines - # /requirements.txt - flask - - Authlib - ``` - - - -
- -
- We're going to use a library called [Authlib](https://github.com/lepture/authlib) to create a [ResourceProtector](https://docs.authlib.org/en/latest/flask/1/resource-server.html), which is a type of [Flask decorator](https://flask.palletsprojects.com/patterns/viewdecorators/) that protects our resources (API routes) with a given validator. - - The validator will validate the Access Token that we pass to the resource by checking that it has a valid - signature and claims. - - We can use AuthLib's `JWTBearerTokenValidator` validator with a few tweaks to make sure it conforms to - our requirements on [validating Access Tokens](https://auth0.com/docs/secure/tokens/access-tokens/validate-access-tokens). - - To create our `Auth0JWTBearerTokenValidator` we need to pass it our `domain` and - `audience` (API Identifier). It will then get the public key required to verify the token's signature - and pass it to the `JWTBearerTokenValidator` class. - - We'll then override the class's `claims_options` to make sure the token's expiry, audience and issue - claims are validated according to our requirements. - - - - - -
- -
- Next we'll create a Flask application with 3 API routes: - - - `/api/public`A public endpoint that requires no authentication. - - `/api/private`A private endpoint that requires a valid Access Token JWT. - - `/api/private-scoped`A private endpoint that requires a valid Access Token JWT that contains the - given `scope`. - - The protected routes will have a `require_auth` decorator which is a `ResourceProtector` - that uses the `Auth0JWTBearerTokenValidator` we created earlier. - - To create the `Auth0JWTBearerTokenValidator` we'll pass it our tenant's domain and the API Identifier - of the API we created earlier. - - The `require_auth` decorator on the `private_scoped` route accepts an additional argument - `"read:messages"`, which checks the Access Token for the Permission (Scope) we created earlier. - - ### Make a Call to Your API - - To make calls to your API, you need an Access Token. You can get an Access Token for testing purposes from the - **Test** view in your [API - settings](https://manage.auth0.com/#/apis). - - - - - - Provide the Access Token as an `Authorization` header in your requests. - - - ```bash cURL lines - curl --request get \ - --url 'http:///%7ByourDomain%7D/api_path' \ - --header 'authorization: Bearer YOUR_ACCESS_TOKEN_HERE' - ``` - - ```csharp C# lines - var client = new RestClient("http:///%7ByourDomain%7D/api_path"); - var request = new RestRequest(Method.GET); - request.AddHeader("authorization", "Bearer YOUR_ACCESS_TOKEN_HERE"); - IRestResponse response = client.Execute(request); - ``` - - ```go Go lines - package main - import ( - "fmt" - "net/http" - "io/ioutil" - ) - func main() { - url := "http:///%7ByourDomain%7D/api_path" - req, _ := http.NewRequest("get", url, nil) - req.Header.Add("authorization", "Bearer YOUR_ACCESS_TOKEN_HERE") - res, _ := http.DefaultClient.Do(req) - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) - fmt.Println(res) - fmt.Println(string(body)) - } - ``` - - ```java Java lines - HttpResponse response = Unirest.get("http:///%7ByourDomain%7D/api_path") - .header("authorization", "Bearer YOUR_ACCESS_TOKEN_HERE") - .asString(); - ``` - - ```javascript Node.JS lines - var axios = require("axios").default; - var options = { - method: 'get', - url: 'http:///%7ByourDomain%7D/api_path', - headers: {authorization: 'Bearer YOUR_ACCESS_TOKEN_HERE'} - }; - axios.request(options).then(function (response) { - console.log(response.data); - }).catch(function (error) { - console.error(error); - }); - ``` - - ```objc Obj-C lines - #import - NSDictionary *headers = @{ @"authorization": @"Bearer YOUR_ACCESS_TOKEN_HERE" }; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http:///%7ByourDomain%7D/api_path"] - cachePolicy:NSURLRequestUseProtocolCachePolicy - - timeoutInterval:10.0]; - - [request setHTTPMethod:@"get"]; - [request setAllHTTPHeaderFields:headers]; - NSURLSession *session = [NSURLSession sharedSession]; - NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - - if (error) { - - NSLog(@"%@", error); - - } else { - - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; - - NSLog(@"%@", httpResponse); - - } - - }]; - - [dataTask resume]; - ``` - - ```php PHP lines - #import - NSDictionary *headers = @{ @"authorization": @"Bearer YOUR_ACCESS_TOKEN_HERE" }; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http:///%7ByourDomain%7D/api_path"] - cachePolicy:NSURLRequestUseProtocolCachePolicy - - timeoutInterval:10.0]; - - [request setHTTPMethod:@"get"]; - [request setAllHTTPHeaderFields:headers]; - NSURLSession *session = [NSURLSession sharedSession]; - NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - - if (error) { - - NSLog(@"%@", error); - - } else { - - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; - - NSLog(@"%@", httpResponse); - - } - - }]; - - [dataTask resume]; - ``` - - ```python Python lines - import http.client - conn = http.client.HTTPConnection("") - headers = { 'authorization': "Bearer YOUR_ACCESS_TOKEN_HERE" } - conn.request("get", "/%7ByourDomain%7D/api_path", headers=headers) - res = conn.getresponse() - data = res.read() - print(data.decode("utf-8")) - ``` - - ```ruby Ruby lines - require 'uri' - require 'net/http' - url = URI("http:///%7ByourDomain%7D/api_path") - http = Net::HTTP.new(url.host, url.port) - request = Net::HTTP::Get.new(url) - request["authorization"] = 'Bearer YOUR_ACCESS_TOKEN_HERE' - response = http.request(request) - puts response.read_body - ``` - - ```swift Swift lines - import Foundation - let headers = ["authorization": "Bearer YOUR_ACCESS_TOKEN_HERE"] - let request = NSMutableURLRequest(url: NSURL(string: "http:///%7ByourDomain%7D/api_path")! as URL, - cachePolicy: .useProtocolCachePolicy, - - timeoutInterval: 10.0) - - request.httpMethod = "get" - request.allHTTPHeaderFields = headers - let session = URLSession.shared - let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in - if (error != nil) { - print(error) - - } else { - let httpResponse = response as? HTTPURLResponse - - print(httpResponse) - - } - }) - dataTask.resume() - ``` - - - - - - - -
- - ## Next Steps - - Excellent work! If you made it this far, you should now have login, logout, and user profile information running in your application. - - This concludes our quickstart tutorial, but there is so much more to explore. To learn more about what you can do with Auth0, check out: - - * [Auth0 Dashboard](https://manage.auth0.com/dashboard/us/dev-gja8kxz4ndtex3rq) - Learn how to configure and manage your Auth0 tenant and applications - * [auth0-python SDK](https://github.com/auth0/auth0-python) - Explore the SDK used in this tutorial more fully - * [Auth0 Marketplace](https://marketplace.auth0.com/) - Discover integrations you can enable to extend Auth0’s functionality -
- - -
diff --git a/main/docs/quickstart/backend/python/index.mdx b/main/docs/quickstart/backend/python/index.mdx index d13978de8a..01351249a9 100644 --- a/main/docs/quickstart/backend/python/index.mdx +++ b/main/docs/quickstart/backend/python/index.mdx @@ -1,278 +1,566 @@ --- -title: "Python API: Authorization" -permalink: "01-authorization" +title: 'Add Authorization to Your Flask API Application' +description: 'This guide demonstrates how to integrate Auth0 with any new or existing Python API built with Flask.' --- -import {AuthCodeBlock} from "/snippets/AuthCodeBlock.jsx"; - -##### By Luciano Balmaceda - -This tutorial demonstrates how to add authorization to a Python API built with Flask.We recommend that you log in to follow this quickstart with examples configured for your account. -{/* -System requirements: Python 3.6 and up | Flask 2.0 | Authlib 1.0 - */} - - -**New to Auth0?** Learn [how Auth0 works](/docs/get-started/auth0-overview) and read about [implementing API authentication and authorization](/docs/get-started/authentication-and-authorization-flow) using the OAuth 2.0 framework. - - -## Configure Auth0 APIs - -### Create an API - -In the [APIs](https://manage.auth0.com/#/apis) section of the Auth0 dashboard, click **Create API**. Provide a name and an identifier for your API, for example, `https://quickstarts/api`. You will use the identifier as an `audience` later, when you are configuring the Access Token verification. Leave the **Signing Algorithm** as **RS256**. - -![Create API](https://cdn2.auth0.com/docs/1.14550.0/media/articles/server-apis/create-api.png) - -By default, your API uses RS256 as the algorithm for signing tokens. Since RS256 uses a private/public keypair, it verifies the tokens against the public key for your Auth0 account. The public key is in the [JSON Web Key Set (JWKS)](/docs/secure/tokens/json-web-tokens/json-web-key-sets) format, and can be accessed [here](https://{yourDomain}/.well-known/jwks.json). - -### Define permissions - -Permissions let you define how resources can be accessed on behalf of the user with a given access token. For example, you might choose to grant read access to the `messages` resource if users have the manager access level, and a write access to that resource if they have the administrator access level. - -You can define allowed permissions in the **Permissions** view of the Auth0 Dashboard's [APIs](https://manage.auth0.com/#/apis) section. - -![Configure Permissions](https://cdn2.auth0.com/docs/1.14550.0/media/articles/server-apis/configure-permissions.png) - - -This example uses the `read:messages` scope. - - -This example demonstrates: - -* How to check for a JSON Web Token (JWT) in the `Authorization` header of an incoming HTTP request. -* How to check if the token is valid, using the [JSON Web Key Set (JWKS)](/docs/secure/tokens/json-web-tokens/json-web-key-sets) for your Auth0 account. To learn more about validating Access Tokens, see [Validate Access Tokens](/docs/secure/tokens/access-tokens/validate-access-tokens). - -## Validate Access Tokens - -### Install dependencies - -Add the following dependencies to your `requirements.txt`: - -```txt lines -# /requirements.txt - -flask==2.3.3 -python-dotenv -pyjwt -flask-cors -six + + **Prerequisites:** Before you begin, ensure you have the following installed: + + - **Python 3.9** or higher + - **pip** or Poetry package manager + - Your preferred code editor + + **Flask Version Compatibility:** This quickstart requires **Flask 3.0** or higher for native async support. + + +## Get Started + +This guide demonstrates how to integrate Auth0 with any new or existing Python API built with [Flask](https://flask.palletsprojects.com/). + + + + Create a new directory for your Flask API: + + ```bash + mkdir flask-auth0-api + cd flask-auth0-api + ``` + + Create a virtual environment and activate it: + + ```bash + python -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` + + + Create a `requirements.txt` file with the following dependencies: + + ```txt requirements.txt + flask>=3.0 + auth0-api-python + python-dotenv + ``` + + Install the dependencies: + + ```bash + pip install -r requirements.txt + ``` + + + Next up, you need to create a new API on your Auth0 tenant and configure your application. + + Alternatively, you can read [our getting started guide](https://auth0.com/docs/get-started/auth0-overview/set-up-apis) that helps you set up your first API through the Auth0 dashboard. + + You can do this manually via the Dashboard or use the Auth0 CLI: + + + + 1. Go to the [Auth0 Dashboard](https://manage.auth0.com/) → **Applications** → **APIs** + 2. Click **Create API** + 3. Enter your API details: + - **Name**: `My Flask API` + - **Identifier**: `https://my-flask-api` (this will be your audience) + - **Signing Algorithm**: **RS256** + 4. Click **Create** + 5. Copy your **Domain** from the Dashboard (found under **Applications** → **Applications** → **[Your App]** → **Settings**) + 6. Copy the **Identifier** you just created (this is your audience) + + + Your **Domain** should not include `https://` - use only the domain name (e.g., `your-tenant.auth0.com`). + + The **Audience** (API Identifier) is a unique identifier for your API and can be any valid URI. + + + + + ```bash + # Install Auth0 CLI + brew tap auth0/auth0-cli && brew install auth0 + + # Login to Auth0 + auth0 login + + # Create API + auth0 apis create \ + --name "My Flask API" \ + --identifier "https://my-flask-api" \ + --signing-alg RS256 + + # Get your domain + auth0 tenants list + ``` + + + + + Create a `.env` file in your project root to store your Auth0 configuration: + + ```bash .env + AUTH0_DOMAIN=your-tenant.us.auth0.com + AUTH0_AUDIENCE=https://my-flask-api + ``` + + + Replace `your-tenant.us.auth0.com` with your actual Auth0 domain and update the audience to match your API identifier. + + + Create an `app.py` file and configure the Auth0 API client: + + ```python app.py lines + import os + from flask import Flask, request, jsonify, g + from functools import wraps + from dotenv import load_dotenv + from auth0_api_python import ApiClient, ApiClientOptions + from auth0_api_python.errors import BaseAuthError + + # Load environment variables + load_dotenv() + + app = Flask(__name__) + + # Initialize Auth0 API client (singleton - created once) + api_client = ApiClient(ApiClientOptions( + domain=os.getenv("AUTH0_DOMAIN"), + audience=os.getenv("AUTH0_AUDIENCE") + )) + ``` + + + Add a decorator for protecting routes and create public and private endpoints: + + ```python app.py + # Authentication decorator + def require_auth(f): + @wraps(f) + async def decorated_function(*args, **kwargs): + auth_header = request.headers.get("Authorization", "") + + if not auth_header.startswith("Bearer "): + return jsonify({"error": "Missing or invalid authorization header"}), 401 + + token = auth_header.split(" ")[1] + + try: + claims = await api_client.verify_access_token(token) + g.user_claims = claims + return await f(*args, **kwargs) + except BaseAuthError as e: + return jsonify({"error": str(e)}), e.get_status_code(), e.get_headers() + + return decorated_function + + + # Public endpoint - no authentication required + @app.route("/api/public", methods=["GET"]) + async def public(): + return jsonify({"message": "This endpoint is public"}) + + + # Protected endpoint - requires authentication + @app.route("/api/private", methods=["GET"]) + @require_auth + async def private(): + return jsonify({ + "message": "This endpoint requires authentication", + "user": g.user_claims["sub"] + }) + + + # Protected endpoint with scope validation + @app.route("/api/admin", methods=["GET"]) + @require_auth + async def admin(): + scopes = g.user_claims.get("scope", "").split() + + if "read:admin" not in scopes: + return jsonify({"error": "Insufficient permissions"}), 403 + + return jsonify({ + "message": "Admin access granted", + "user": g.user_claims["sub"] + }) + + + if __name__ == "__main__": + app.run(debug=True, port=5000) + ``` + + + Start your Flask application: + + ```bash + python app.py + ``` + + Your API is now running on `http://localhost:5000`. + + + + + **Checkpoint** + + You should now have a fully functional Auth0-protected Flask API running on your localhost with three endpoints: + - `/api/public` - Accessible without authentication + - `/api/private` - Requires a valid Auth0 access token + - `/api/admin` - Requires authentication and the `read:admin` scope + + +## Test Your API + +To test your protected endpoints, you need an access token. + +### Get a test token + +1. Go to the [Auth0 Dashboard](https://manage.auth0.com/) +2. Navigate to **Applications → APIs** +3. Select your API +4. Go to the **Test** tab +5. Copy the access token + +### Make a request + +Test the public endpoint (no token required): + +```bash +curl http://localhost:5000/api/public ``` +Test the protected endpoint (token required): +```bash +curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ + http://localhost:5000/api/private +``` - - - -### Create a Flask application - -Create a `server.py` file and initialize the [Flask](http://flask.pocoo.org/) application. Set the domain, audience and the error handling. - -export const codeExample = `# /server.py - -import json -from six.moves.urllib.request import urlopen +Replace `YOUR_ACCESS_TOKEN` with the token you copied from the Auth0 Dashboard. + +## Advanced Usage + + + For applications where most endpoints require authentication, use Flask's `before_request` to validate tokens globally: + + ```python + from flask import g + + PUBLIC_ROUTES = ["/api/public", "/health"] + + @app.before_request + async def verify_token(): + # Skip auth for public routes + if request.path in PUBLIC_ROUTES: + return + + auth_header = request.headers.get("Authorization", "") + + if not auth_header.startswith("Bearer "): + return jsonify({"error": "Missing authorization"}), 401 + + token = auth_header.split(" ")[1] + + try: + claims = await api_client.verify_access_token(token) + g.user_claims = claims + except BaseAuthError as e: + return jsonify({"error": str(e)}), e.get_status_code(), e.get_headers() + + + @app.route("/api/protected-data") + async def protected_data(): + # Claims automatically available via g.user_claims + return jsonify({"user_id": g.user_claims["sub"]}) + ``` + + + + Require specific claims to be present in the access token: + + ```python + try: + claims = await api_client.verify_access_token( + access_token=token, + required_claims=["email_verified", "org_id"] + ) + except BaseAuthError as e: + return jsonify({"error": "Missing required claims"}), 401 + ``` + + + + For larger applications, use Flask's application factory pattern: + + ```python app/__init__.py + from flask import Flask + from auth0_api_python import ApiClient, ApiClientOptions + import os + + api_client = None + + def create_app(config=None): + app = Flask(__name__) + + # Load configuration + if config: + app.config.from_object(config) + else: + app.config.from_mapping( + AUTH0_DOMAIN=os.getenv("AUTH0_DOMAIN"), + AUTH0_AUDIENCE=os.getenv("AUTH0_AUDIENCE") + ) + + # Initialize Auth0 API client + global api_client + api_client = ApiClient(ApiClientOptions( + domain=app.config["AUTH0_DOMAIN"], + audience=app.config["AUTH0_AUDIENCE"] + )) + + # Register blueprints + from .routes import api_bp + app.register_blueprint(api_bp, url_prefix="/api") + + return app + ``` + + ```python app/routes.py +from flask import Blueprint, request, jsonify, g from functools import wraps +from . import api_client +from auth0_api_python.errors import BaseAuthError -from flask import Flask, request, jsonify, _request_ctx_stack -from flask_cors import cross_origin -import jwt - -AUTH0_DOMAIN = '{yourDomain}' -API_AUDIENCE = YOUR_API_AUDIENCE -ALGORITHMS = ["RS256"] - -APP = Flask(__name__) - -# Error handler -class AuthError(Exception): - def __init__(self, error, status_code): - self.error = error - self.status_code = status_code - -@APP.errorhandler(AuthError) -def handle_auth_error(ex): - response = jsonify(ex.error) - response.status_code = ex.status_code - return response`; - - - - +api_bp = Blueprint("api", __name__) - - - -### Create the JWT validation decorator - -Add a decorator which verifies the Access Token against your JWKS. - -```py Python lines -# /server.py - -# Format error response and append status code -def get_token_auth_header(): - """Obtains the Access Token from the Authorization Header - """ - auth = request.headers.get("Authorization", None) - if not auth: - raise AuthError({"code": "authorization_header_missing", - "description": - "Authorization header is expected"}, 401) - - parts = auth.split() - - if parts[0].lower() != "bearer": - raise AuthError({"code": "invalid_header", - "description": - "Authorization header must start with" - " Bearer"}, 401) - elif len(parts) == 1: - raise AuthError({"code": "invalid_header", - "description": "Token not found"}, 401) - elif len(parts) > 2: - raise AuthError({"code": "invalid_header", - "description": - "Authorization header must be" - " Bearer token"}, 401) - - token = parts[1] - return token - -def requires_auth(f): - """Determines if the Access Token is valid - """ +def require_auth(f): @wraps(f) - def decorated(*args, **kwargs): - token = get_token_auth_header() - jsonurl = urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json") - jwks = json.loads(jsonurl.read()) - unverified_header = jwt.get_unverified_header(token) - public_key = None - for key in jwks["keys"]: - if key["kid"] == unverified_header["kid"]: - public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk)) - if public_key: - try: - payload = jwt.decode( - token, - public_key, - algorithms=ALGORITHMS, - audience=API_AUDIENCE, - issuer="https://"+AUTH0_DOMAIN+"/" - ) - except jwt.ExpiredSignatureError: - raise AuthError({"code": "token_expired", - "description": "token is expired"}, 401) - except jwt.InvalidAudienceError: - raise AuthError({"code": "invalid_audience", - "description": - "incorrect audience," - " please check the audience"}, 401) - except jwt.InvalidIssuerError - raise AuthError({"code": "invalid_issuer", - "description": - "incorrect issuer," - " please check the issuer"}, 401) - except Exception: - raise AuthError({"code": "invalid_header", - "description": - "Unable to parse authentication" - " token."}, 401) - - _request_ctx_stack.top.current_user = payload - return f(*args, **kwargs) - raise AuthError({"code": "invalid_header", - "description": "Unable to find appropriate key"}, 401) -    return decorated + async def decorated_function(*args, **kwargs): + auth_header = request.headers.get("Authorization", "") + + if not auth_header.startswith("Bearer "): + return jsonify({"error": "Missing token"}), 401 + + token = auth_header.split(" ")[1] + + try: + claims = await api_client.verify_access_token(token) + g.user_claims = claims + return await f(*args, **kwargs) + except BaseAuthError as e: + return jsonify({"error": str(e)}), e.get_status_code(), e.get_headers() + + return decorated_function + + +@api_bp.route("/protected") +@require_auth +async def protected(): + return jsonify({"user_id": g.user_claims["sub"]}) ``` - - - - - - -### Validate scopes - -Individual routes can be configured to look for a particular `scope` in the Access Token by using the following: - -```py Python lines -# /server.py - -def requires_scope(required_scope): - """Determines if the required scope is present in the Access Token - Args: - required_scope (str): The scope required to access the resource - """ - token = get_token_auth_header() - unverified_claims = jwt.decode(token, options={"verify_signature": False}) - if unverified_claims.get("scope"): - token_scopes = unverified_claims["scope"].split() - for token_scope in token_scopes: - if token_scope == required_scope: - return True - return False + + + + For enhanced security, enable DPoP (Demonstrating Proof-of-Possession): + + ```python + # Configure API client with DPoP enabled + api_client = ApiClient(ApiClientOptions( + domain=os.getenv("AUTH0_DOMAIN"), + audience=os.getenv("AUTH0_AUDIENCE"), + dpop_enabled=True # Default, accepts both Bearer and DPoP + )) + + @app.route("/api/dpop-protected") + @require_auth + async def dpop_protected(): + headers = { + "authorization": request.headers.get("Authorization", ""), + "dpop": request.headers.get("DPoP", "") + } + + try: + claims = await api_client.verify_request( + headers=headers, + http_method=request.method, + http_url=request.url + ) + return jsonify({"user": claims["sub"]}) + except BaseAuthError as e: + return jsonify({"error": str(e)}), e.get_status_code(), e.get_headers() + ``` + + + + Create a decorator to check for specific scopes: + + ```python + def require_scope(required_scope): + def decorator(f): + @wraps(f) + async def wrapper(*args, **kwargs): + if not hasattr(g, "user_claims"): + return jsonify({"error": "Unauthorized"}), 401 + + scopes = g.user_claims.get("scope", "").split() + + if required_scope not in scopes: + return jsonify({"error": "Insufficient permissions"}), 403 + + return await f(*args, **kwargs) + return wrapper + return decorator + + + @app.route("/api/admin") + @require_auth + @require_scope("admin:write") + async def admin_endpoint(): + return jsonify({"message": "Admin access granted"}) + ``` + + + + Implement comprehensive error handling: + + ```python + from auth0_api_python.errors import ( + VerifyAccessTokenError, + MissingRequiredArgumentError, + ApiError + ) + + @app.errorhandler(BaseAuthError) + async def handle_auth_error(error): + return jsonify({ + "error": error.get_error_code(), + "error_description": str(error) + }), error.get_status_code(), error.get_headers() + + + @app.errorhandler(Exception) + async def handle_generic_error(error): + app.logger.error(f"Unexpected error: {str(error)}") + return jsonify({"error": "Internal server error"}), 500 + ``` + + +## Common Issues + + + + **Symptom**: Getting 401 errors even with valid-looking tokens + + **Cause**: The audience in your token doesn't match the audience configured in your API client + + **Solution**: + 1. Verify the `AUTH0_AUDIENCE` in your `.env` file matches your Auth0 API Identifier exactly + 2. The audience is case-sensitive + 3. Ensure the audience is a URL or URN format (e.g., `https://my-api` not `my-api`) + + + + **Symptom**: Token validation fails with issuer mismatch + + **Cause**: The domain configuration doesn't match the token issuer + + **Solution**: + 1. Verify `AUTH0_DOMAIN` is correct (e.g., `tenant.us.auth0.com`) + 2. Don't include `https://` in the domain + 3. Don't include a trailing slash + + + + **Symptom**: `None` values or environment variable errors + + **Cause**: Environment variables not loaded or `.env` file not found + + **Solution**: + 1. Ensure `.env` file exists in your project root + 2. Verify `load_dotenv()` is called before accessing `os.getenv()` + 3. Check that variable names match exactly (case-sensitive) + + + + **Symptom**: `RuntimeError: This event loop is already running` or similar async errors + + **Cause**: Using async routes without Flask 3.0+ or mixing sync/async incorrectly + + **Solution**: + 1. Upgrade to Flask 3.0 or higher: `pip install --upgrade flask` + 2. Ensure all route handlers using `api_client` are declared as `async def` + 3. Don't use `asyncio.run()` within route handlers + + + + **Symptom**: `VerifyAccessTokenError: Token is expired` + + **Cause**: The access token has passed its expiration time + + **Solution**: + 1. Request a new token from the Auth0 Dashboard Test tab + 2. Implement token refresh in your client application + 3. Tokens from the Dashboard are typically valid for 24 hours + + + + **Symptom**: `Missing or invalid authorization header` error + + **Cause**: Request doesn't include the `Authorization` header or uses incorrect format + + **Solution**: + 1. Ensure the header is named `Authorization` (capital A) + 2. Use format: `Authorization: Bearer YOUR_TOKEN` + 3. Don't include quotes around the token + + + +## Additional Resources + + + + Complete SDK documentation and API reference + + + Official Flask framework documentation + + + Manage your Auth0 tenant and APIs + + + Learn about access tokens and API security + + + Additional Python integration patterns + + + Learn about proof-of-possession security + + + Get help from the Auth0 community + + + +## Sample Application + +A complete sample application is available in the SDK repository with additional features: + +- Public and protected endpoints +- Scope-based authorization +- Error handling middleware +- Environment configuration +- Application factory pattern + +Clone and run: + +```bash +git clone https://github.com/auth0/auth0-api-python.git +cd auth0-api-python/examples/flask +pip install -r requirements.txt + +# Update .env with your Auth0 configuration +cp .env.example .env + +# Run the application +python app.py ``` - - - - - -## Protect API Endpoints - -The routes shown below are available for the following requests: - -* `GET /api/public`: available for non-authenticated requests -* `GET /api/private`: available for authenticated requests containing an access token with no additional scopes -* `GET /api/private-scoped`: available for authenticated requests containing an access token with the `read:messages` scope granted - -You can use the decorators and functions define above in the corresponding endpoints. - -```py Python lines -# Controllers API - -# This doesn't need authentication -@APP.route("/api/public") -@cross_origin(headers=["Content-Type", "Authorization"]) -def public(): - response = "Hello from a public endpoint! You don't need to be authenticated to see this." - return jsonify(message=response) - -# This needs authentication -@APP.route("/api/private") -@cross_origin(headers=["Content-Type", "Authorization"]) -@requires_auth -def private(): - response = "Hello from a private endpoint! You need to be authenticated to see this." - return jsonify(message=response) - -# This needs authorization -@APP.route("/api/private-scoped") -@cross_origin(headers=["Content-Type", "Authorization"]) -@requires_auth -def private_scoped(): - if requires_scope("read:messages"): - response = "Hello from a private endpoint! You need to be authenticated and have a scope of read:messages to see this." - return jsonify(message=response) - raise AuthError({ - "code": "Unauthorized", - "description": "You don't have access to this resource" - }, 403) -``` - - -##### What can you do next? - - - - - - - - - -
Configure other identity providersEnable multifactor authentication
Learn about attack protectionLearn about rules
-[Edit on GitHub](https://github.com/auth0/docs/edit/master/articles/quickstart/backend/aspnet-core-webapi/01-authorization.md) -
---- diff --git a/main/docs/quickstart/backend/python/interactive.mdx b/main/docs/quickstart/backend/python/interactive.mdx deleted file mode 100644 index 30916031ca..0000000000 --- a/main/docs/quickstart/backend/python/interactive.mdx +++ /dev/null @@ -1,326 +0,0 @@ ---- - -mode: wide -description: This guide demonstrates how to integrate Auth0 with any new or existing - Python API built with Flask. -'og:image': https://cdn2.auth0.com/docs/1.14553.0/img/share-image.png -'og:title': 'Auth0 Python API SDK Quickstarts: Add Authorization to Your Flask API - Application' -'og:url': https://auth0.com/docs/ -sidebarTitle: Python API -title: Add Authorization to Your Flask API Application -'twitter:description': This guide demonstrates how to integrate Auth0 with any new - or existing Python API built with Flask. -'twitter:title': 'Auth0 Python API SDK Quickstarts: Add Authorization to Your Flask - API Application' ---- -import { Recipe, Content, Section, SideMenu, SideMenuSectionItem, SignUpForm } from "/snippets/recipe.jsx"; -import { LoggedInForm } from "/snippets/Login.jsx"; -import Validator from "/snippets/quickstart/backend/python/validator.py.mdx"; -import Server from "/snippets/quickstart/backend/python/server.py.mdx"; - -import {QuickstartButtons} from "/snippets/QuickstartButtons.jsx"; - -import {AuthCodeGroup} from "/snippets/AuthCodeGroup.jsx"; - - - -export const sections = [ - { id: "define-permissions", title: "Define permissions" }, - { id: "install-dependencies", title: "Install dependencies" }, - { id: "create-the-jwt-validator", title: "Create the JWT validator" }, - { id: "create-a-flask-application", title: "Create a Flask application" } -] - - - - This guide demonstrates how to integrate Auth0 with any new or existing Python API built with [Flask](https://flask.palletsprojects.com/). - - If you haven't created an API in your Auth0 dashboard yet, you can use the interactive selector to create a new - Auth0 API or select an existing API that represents the project you want to integrate with. - - Alternatively, you can read [our getting started guide](https://auth0.com/docs/get-started/auth0-overview/set-up-apis) that helps you set up your first API through the Auth0 dashboard. - - Every API in Auth0 is configured using an API Identifier that your application code will use as the Audience to - validate the Access Token. - - - **New to Auth0?** Learn [how Auth0 works](https://auth0.com/docs/overview) - and read about [implementing API authentication - and authorization](https://auth0.com/docs/api-auth) using the OAuth 2.0 framework. - - -
- Permissions let you define how resources can be accessed on behalf of the user with a given access token. For - example, you might choose to grant read access to the `messages` resource if users have the manager - access level, and a write access to that resource if they have the administrator access level. - - You can define allowed permissions in the **Permissions** view of the Auth0 Dashboard's [APIs](https://manage.auth0.com/#/apis) section. - - - - - - - This example uses the `read:messages` scope. - -
- -
- Add the following dependencies to your `requirements.txt`: - - ```txt lines - # /requirements.txt - flask - - Authlib - ``` - -
- -
- We're going to use a library called [Authlib](https://github.com/lepture/authlib) to create a [ResourceProtector](https://docs.authlib.org/en/latest/flask/1/resource-server.html), which is a type of [Flask decorator](https://flask.palletsprojects.com/patterns/viewdecorators/) that protects our resources (API routes) with a given validator. - - The validator will validate the Access Token that we pass to the resource by checking that it has a valid - signature and claims. - - We can use AuthLib's `JWTBearerTokenValidator` validator with a few tweaks to make sure it conforms to - our requirements on [validating Access Tokens](https://auth0.com/docs/secure/tokens/access-tokens/validate-access-tokens). - - To create our `Auth0JWTBearerTokenValidator` we need to pass it our `domain` and - `audience` (API Identifier). It will then get the public key required to verify the token's signature - and pass it to the `JWTBearerTokenValidator` class. - - We'll then override the class's `claims_options` to make sure the token's expiry, audience and issue - claims are validated according to our requirements. -
- -
- Next we'll create a Flask application with 3 API routes: - - - `/api/public`A public endpoint that requires no authentication. - - `/api/private`A private endpoint that requires a valid Access Token JWT. - - `/api/private-scoped`A private endpoint that requires a valid Access Token JWT that contains the - given `scope`. - - The protected routes will have a `require_auth` decorator which is a `ResourceProtector` - that uses the `Auth0JWTBearerTokenValidator` we created earlier. - - To create the `Auth0JWTBearerTokenValidator` we'll pass it our tenant's domain and the API Identifier - of the API we created earlier. - - The `require_auth` decorator on the `private_scoped` route accepts an additional argument - `"read:messages"`, which checks the Access Token for the Permission (Scope) we created earlier. - - ### Make a Call to Your API - - To make calls to your API, you need an Access Token. You can get an Access Token for testing purposes from the - **Test** view in your [API - settings](https://manage.auth0.com/#/apis). - - - - - - Provide the Access Token as an `Authorization` header in your requests. - - - ```bash cURL lines - curl --request get \ - --url 'http:///%7ByourDomain%7D/api_path' \ - --header 'authorization: Bearer YOUR_ACCESS_TOKEN_HERE' - ``` - - ```csharp C# lines - var client = new RestClient("http:///%7ByourDomain%7D/api_path"); - var request = new RestRequest(Method.GET); - request.AddHeader("authorization", "Bearer YOUR_ACCESS_TOKEN_HERE"); - IRestResponse response = client.Execute(request); - ``` - - ```go Go lines - package main - import ( - "fmt" - "net/http" - "io/ioutil" - ) - func main() { - url := "http:///%7ByourDomain%7D/api_path" - req, _ := http.NewRequest("get", url, nil) - req.Header.Add("authorization", "Bearer YOUR_ACCESS_TOKEN_HERE") - res, _ := http.DefaultClient.Do(req) - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) - fmt.Println(res) - fmt.Println(string(body)) - } - ``` - - ```java Java lines - HttpResponse response = Unirest.get("http:///%7ByourDomain%7D/api_path") - .header("authorization", "Bearer YOUR_ACCESS_TOKEN_HERE") - .asString(); - ``` - - ```javascript Node.JS lines - var axios = require("axios").default; - var options = { - method: 'get', - url: 'http:///%7ByourDomain%7D/api_path', - headers: {authorization: 'Bearer YOUR_ACCESS_TOKEN_HERE'} - }; - axios.request(options).then(function (response) { - console.log(response.data); - }).catch(function (error) { - console.error(error); - }); - ``` - - ```objc Obj-C lines - #import - NSDictionary *headers = @{ @"authorization": @"Bearer YOUR_ACCESS_TOKEN_HERE" }; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http:///%7ByourDomain%7D/api_path"] - cachePolicy:NSURLRequestUseProtocolCachePolicy - - timeoutInterval:10.0]; - - [request setHTTPMethod:@"get"]; - [request setAllHTTPHeaderFields:headers]; - NSURLSession *session = [NSURLSession sharedSession]; - NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - - if (error) { - - NSLog(@"%@", error); - - } else { - - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; - - NSLog(@"%@", httpResponse); - - } - - }]; - - [dataTask resume]; - ``` - - ```php PHP lines - #import - NSDictionary *headers = @{ @"authorization": @"Bearer YOUR_ACCESS_TOKEN_HERE" }; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http:///%7ByourDomain%7D/api_path"] - cachePolicy:NSURLRequestUseProtocolCachePolicy - - timeoutInterval:10.0]; - - [request setHTTPMethod:@"get"]; - [request setAllHTTPHeaderFields:headers]; - NSURLSession *session = [NSURLSession sharedSession]; - NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - - if (error) { - - NSLog(@"%@", error); - - } else { - - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; - - NSLog(@"%@", httpResponse); - - } - - }]; - - [dataTask resume]; - ``` - - ```python Python lines - import http.client - conn = http.client.HTTPConnection("") - headers = { 'authorization': "Bearer YOUR_ACCESS_TOKEN_HERE" } - conn.request("get", "/%7ByourDomain%7D/api_path", headers=headers) - res = conn.getresponse() - data = res.read() - print(data.decode("utf-8")) - ``` - - ```ruby Ruby lines - require 'uri' - require 'net/http' - url = URI("http:///%7ByourDomain%7D/api_path") - http = Net::HTTP.new(url.host, url.port) - request = Net::HTTP::Get.new(url) - request["authorization"] = 'Bearer YOUR_ACCESS_TOKEN_HERE' - response = http.request(request) - puts response.read_body - ``` - - ```swift Swift lines - import Foundation - let headers = ["authorization": "Bearer YOUR_ACCESS_TOKEN_HERE"] - let request = NSMutableURLRequest(url: NSURL(string: "http:///%7ByourDomain%7D/api_path")! as URL, - cachePolicy: .useProtocolCachePolicy, - - timeoutInterval: 10.0) - - request.httpMethod = "get" - request.allHTTPHeaderFields = headers - let session = URLSession.shared - let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in - if (error != nil) { - print(error) - - } else { - let httpResponse = response as? HTTPURLResponse - - print(httpResponse) - - } - }) - dataTask.resume() - ``` - - -
- - ## Next Steps - - Excellent work! If you made it this far, you should now have login, logout, and user profile information running in your application. - - This concludes our quickstart tutorial, but there is so much more to explore. To learn more about what you can do with Auth0, check out: - - * [Auth0 Dashboard](https://manage.auth0.com/dashboard/us/dev-gja8kxz4ndtex3rq) - Learn how to configure and manage your Auth0 tenant and applications - * [auth0-python SDK](https://github.com/auth0/auth0-python) - Explore the SDK used in this tutorial more fully - * [Auth0 Marketplace](https://marketplace.auth0.com/) - Discover integrations you can enable to extend Auth0’s functionality -
- - - - - - - - - - - - - - - - - - - - - - - - - -