0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat(console): add api resource guide for python (#4518)

This commit is contained in:
Charles Zhao 2023-09-15 18:56:05 +08:00 committed by GitHub
parent 0995b4257c
commit d1c25b9c26
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 167 additions and 0 deletions

View file

@ -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';
<Steps>
<Step title="Extract the Bearer Token from request header">
```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]
```
</Step>
<Step title="Extract the Bearer Token from request header" subtitle="3 steps">
### 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
<p>
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 <code>{appendPath(props.endpoint, '/oidc/.well-known/openid-configuration')}</code>.
e.g. You can locate the following two fields in the response body if you request the above endpoint.
</p>
<pre>
<code className="language-json">
{`{
"issuer": "${appendPath(props.endpoint, '/oidc')}",
"jwks_uri": "${appendPath(props.endpoint, '/oidc/jwks')}"
}`}
</code>
</pre>
### Create the authorization validation decorator
<pre>
<code className="language-python">
{`"""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`}
</code>
</pre>
<InlineNotification>
For <a href="https://docs.logto.io/docs/recipes/rbac/" target="_blank" rel="noopener">🔐 RBAC</a>, scope validation is also required.
</InlineNotification>
</Step>
<Step title="Apply decorator to your API">
```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
```
</Step>
</Steps>

View file

@ -0,0 +1,9 @@
import { type GuideMetadata } from '../types';
const metadata: Readonly<GuideMetadata> = Object.freeze({
name: 'Python',
description: 'Integrate Logto into your Python web app, such as Django and Flask.',
target: 'API',
});
export default metadata;

View file

@ -0,0 +1,14 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.885 8C15.761 8 16.268 11.523 16.268 11.523L16.278 15.173H24.03V16.268H13.197C13.197 16.268 8 15.678 8 23.876C8 32.072 12.537 31.782 12.537 31.782H15.245V27.978C15.245 27.978 15.099 23.441 19.71 23.441H27.398C27.398 23.441 31.718 23.511 31.718 19.266V12.247C31.718 12.247 32.374 8 23.885 8ZM19.61 10.454C20.381 10.454 21.005 11.078 21.005 11.849C21.005 12.62 20.381 13.244 19.61 13.244C19.4267 13.2443 19.2452 13.2084 19.0758 13.1383C18.9065 13.0683 18.7526 12.9656 18.623 12.836C18.4934 12.7064 18.3907 12.5525 18.3207 12.3832C18.2506 12.2138 18.2147 12.0323 18.215 11.849C18.215 11.078 18.839 10.454 19.61 10.454Z" fill="url(#paint0_linear_129_42168)"/>
<path d="M24.115 39.833C32.239 39.833 31.732 36.31 31.732 36.31L31.722 32.66H23.97V31.565H34.802C34.802 31.565 40 32.155 40 23.958C40 15.761 35.463 16.052 35.463 16.052H32.755V19.855C32.755 19.855 32.901 24.392 28.29 24.392H20.602C20.602 24.392 16.282 24.322 16.282 28.567V35.586C16.282 35.586 15.626 39.833 24.115 39.833ZM28.39 37.379C28.2068 37.3793 28.0252 37.3434 27.8559 37.2734C27.6865 37.2033 27.5326 37.1006 27.403 36.971C27.2734 36.8414 27.1707 36.6875 27.1007 36.5182C27.0307 36.3488 26.9948 36.1673 26.995 35.984C26.995 35.214 27.619 34.59 28.39 34.59C29.161 34.59 29.785 35.213 29.785 35.984C29.785 36.756 29.161 37.379 28.39 37.379Z" fill="url(#paint1_linear_129_42168)"/>
<defs>
<linearGradient id="paint0_linear_129_42168" x1="11.075" y1="10.782" x2="26.898" y2="26.658" gradientUnits="userSpaceOnUse">
<stop stop-color="#387EB8"/>
<stop offset="1" stop-color="#366994"/>
</linearGradient>
<linearGradient id="paint1_linear_129_42168" x1="20.809" y1="20.882" x2="37.803" y2="37.163" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFE052"/>
<stop offset="1" stop-color="#FFC331"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -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<Guide[]> = 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',