diff --git a/packages/console/src/assets/docs/guides/api-python/README.mdx b/packages/console/src/assets/docs/guides/api-python/README.mdx new file mode 100644 index 000000000..cc6c362a2 --- /dev/null +++ b/packages/console/src/assets/docs/guides/api-python/README.mdx @@ -0,0 +1,136 @@ +import Tabs from '@mdx/components/Tabs'; +import TabItem from '@mdx/components/TabItem'; +import InlineNotification from '@/ds-components/InlineNotification'; +import Steps from '@/mdx-components/Steps'; +import Step from '@/mdx-components/Step'; +import { appendPath } from '@silverhand/essentials'; + + + + + +```python +"""requires-auth.py +""" +def get_auth_token(): + auth = request.headers.get("Authorization", None) + + if not auth: + raise Error({ code: 'auth.authorization_header_missing', status: 401 }) + + contents = auth.split() + + if len(contents) < 2 + raise Error({code: 'auth.authorization_token_invalid_format', status: 401}) + + elif contents[0] != 'Bearer' + raise Error({code: 'auth.authorization_token_type_not_supported', status: 401}) + + return contents[1] +``` + + + + + +### Install `python-jose` as your dependency + +Pick the cryptography your are using in Logto. (`ecdsa` by default) + +```bash +pip install python-jose[ecdsa] +``` + +### Retrieve Logto's OIDC configurations + +

+You will need a JWK public key set and the token issuer to verify the signature and source of the received JWS token. +All the latest public Logto Authorization Configurations can be found at {appendPath(props.endpoint, '/oidc/.well-known/openid-configuration')}. + +e.g. You can locate the following two fields in the response body if you request the above endpoint. +

+ +
+  
+{`{
+  "issuer": "${appendPath(props.endpoint, '/oidc')}",
+  "jwks_uri": "${appendPath(props.endpoint, '/oidc/jwks')}"
+}`}
+  
+
+ +### Create the authorization validation decorator + +
+  
+{`"""requires-auth.py
+"""
+
+import json
+from flask import request,  _request_ctx_stack
+from six.moves.urllib.request import urlopen
+from functools import wraps
+from jose import jwt
+
+def requires_auth(f):
+  @wraps(f)
+  def decorated(*args, **kwargs):
+    token = get_token_auth_header()
+
+    # jwks_uri endpoint retrieved from Logto
+    jwks_uri = urlopen('${appendPath(props.endpoint, '/oidc/jwks')}')
+
+    # issuer retrieved from Logto
+    issuer = '${appendPath(props.endpoint, '/oidc')}'
+
+    jwks = json.loads(jwks_uri.read())
+
+    try:
+      payload = jwt.decode(
+        token,
+        jwks,
+        # The jwt encode algorithm retrieved along with jwks. ES384 by default
+        algorithms=jwt.get_unverified_header(token).get('alg'),
+        # The API's registered resource indicator in Logto
+        audience='${props.audience}',
+        issuer=issuer,
+        options={
+          'verify_at_hash': False
+        }
+      )
+    except Exception:
+      # exception handler
+      raise Error({code: 'invalid_token', status: 401})
+
+      # Custom code to process payload
+    _request_ctx_stack.top.user_id = payload.get('sub')
+
+    return f(*args, **kwargs)
+  return decorated`}
+  
+
+ + + For 🔐 RBAC, scope validation is also required. + + +
+ + + +```python +from flask import Flask +from flask_cors import cross_origin + +APP = Flask(__name__) + +@APP.route("/user/info") +@cross_origin(headers=["Content-Type", "Authorization"]) +@requires_auth +def api: + # Your API Logic +``` + + + +
diff --git a/packages/console/src/assets/docs/guides/api-python/index.ts b/packages/console/src/assets/docs/guides/api-python/index.ts new file mode 100644 index 000000000..8a1d0d18c --- /dev/null +++ b/packages/console/src/assets/docs/guides/api-python/index.ts @@ -0,0 +1,9 @@ +import { type GuideMetadata } from '../types'; + +const metadata: Readonly = Object.freeze({ + name: 'Python', + description: 'Integrate Logto into your Python web app, such as Django and Flask.', + target: 'API', +}); + +export default metadata; diff --git a/packages/console/src/assets/docs/guides/api-python/logo.svg b/packages/console/src/assets/docs/guides/api-python/logo.svg new file mode 100644 index 000000000..c33c339ec --- /dev/null +++ b/packages/console/src/assets/docs/guides/api-python/logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/console/src/assets/docs/guides/index.ts b/packages/console/src/assets/docs/guides/index.ts index 354a1eb6a..0bc6c0e50 100644 --- a/packages/console/src/assets/docs/guides/index.ts +++ b/packages/console/src/assets/docs/guides/index.ts @@ -3,6 +3,7 @@ import { lazy } from 'react'; import apiExpress from './api-express/index'; +import apiPython from './api-python/index'; import apiSpringBoot from './api-spring-boot/index'; import m2mGeneral from './m2m-general/index'; import nativeAndroidJava from './native-android-java/index'; @@ -174,6 +175,13 @@ const guides: Readonly = Object.freeze([ Component: lazy(async () => import('./api-express/README.mdx')), metadata: apiExpress, }, + { + order: Number.POSITIVE_INFINITY, + id: 'api-python', + Logo: lazy(async () => import('./api-python/logo.svg')), + Component: lazy(async () => import('./api-python/README.mdx')), + metadata: apiPython, + }, { order: Number.POSITIVE_INFINITY, id: 'api-spring-boot',