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:
parent
0995b4257c
commit
d1c25b9c26
4 changed files with 167 additions and 0 deletions
136
packages/console/src/assets/docs/guides/api-python/README.mdx
Normal file
136
packages/console/src/assets/docs/guides/api-python/README.mdx
Normal 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>
|
|
@ -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;
|
14
packages/console/src/assets/docs/guides/api-python/logo.svg
Normal file
14
packages/console/src/assets/docs/guides/api-python/logo.svg
Normal 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 |
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue