diff --git a/packages/console/src/assets/docs/guides/api-express/README.mdx b/packages/console/src/assets/docs/guides/api-express/README.mdx
new file mode 100644
index 000000000..d66708bbb
--- /dev/null
+++ b/packages/console/src/assets/docs/guides/api-express/README.mdx
@@ -0,0 +1,134 @@
+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';
+
+
+
+
+
+A authorized request should contain an Authorization header with Bearer `` as its content. Extract the Authorization Token from the request header:
+
+```ts
+// auth_middleware.ts
+
+import { IncomingHttpHeaders } from 'http';
+
+const extractBearerTokenFromHeaders = ({ authorization }: IncomingHttpHeaders) => {
+ if (!authorization) {
+ throw new Error({ code: 'auth.authorization_header_missing', status: 401 });
+ }
+
+ if (!authorization.startsWith('Bearer')) {
+ throw new Error({ code: 'auth.authorization_token_type_not_supported', status: 401 });
+ }
+
+ return authorization.slice(bearerTokenIdentifier.length + 1);
+};
+```
+
+
+
+
+
+
+ For demonstration, we use jose package to validate the token's signature, expiration status, and required claims.
+
+
+### Install `jose` as dependency
+
+
+
+
+```bash
+npm install jose
+```
+
+
+
+
+```bash
+yarn add jose
+```
+
+
+
+
+```bash
+pnpm add jose
+```
+
+
+
+
+### 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 `https:///oidc/.well-known/openid-configuration`.
+
+e.g. Call `https://logto.dev/oidc/.well-known/openid-configuration`. And locate the following two fields in the response body:
+
+```ts
+{
+ "jwks_uri": "https://logto.dev/oidc/jwks",
+ "issuer": "https://logto.dev/oidc"
+}
+```
+
+### Add auth middleware
+
+Jose's `jwtVerify` method may helps you to verify the token's JWS format, token signature, issuer, audience and the expiration status. A exception will be thrown if validation failed.
+
+
+ For 🔐 RBAC, scope validation is also required.
+
+
+```ts
+// auth-middleware.ts
+
+import { createRemoteJWKSet, jwtVerify } from 'jose';
+
+//...
+
+export const verifyAuthFromRequest = async (req, res, next) => {
+ // Extract the token
+ const token = extractBearerTokenFromHeaders(req.headers);
+
+ const { payload } = await jwtVerify(
+ // The raw Bearer Token extracted from the request header
+ token,
+ // Generate a jwks using jwks_uri inquired from Logto server
+ createRemoteJWKSet('https:///oidc/jwks'),
+ {
+ // Expected issuer of the token, should be issued by the Logto server
+ issuer: 'https:///oidc',
+ // Expected audience token, should be the resource indicator of the current API
+ audience: '',
+ }
+ );
+
+ // If you are using RBAC
+ assert(payload.scope.includes('some_scope'));
+
+ // Custom payload logic
+ userId = payload.sub;
+
+ return next();
+};
+```
+
+
+
+
+
+```ts
+import { verifyAuthFromRequest } from '/middleware/auth-middleware.ts';
+
+app.get('/user/:id', verifyAuthFromRequest, (req, res, next) => {
+ // Custom code
+});
+```
+
+
+
+
diff --git a/packages/console/src/assets/docs/guides/api-express/index.ts b/packages/console/src/assets/docs/guides/api-express/index.ts
new file mode 100644
index 000000000..e5f14a72b
--- /dev/null
+++ b/packages/console/src/assets/docs/guides/api-express/index.ts
@@ -0,0 +1,9 @@
+import { type GuideMetadata } from '../types';
+
+const metadata: Readonly = Object.freeze({
+ name: 'Express',
+ description: 'Protect your API on node (Express)',
+ target: 'API',
+});
+
+export default metadata;
diff --git a/packages/console/src/assets/docs/guides/api-express/logo.svg b/packages/console/src/assets/docs/guides/api-express/logo.svg
new file mode 100644
index 000000000..bda5f80da
--- /dev/null
+++ b/packages/console/src/assets/docs/guides/api-express/logo.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/console/src/assets/docs/guides/index.ts b/packages/console/src/assets/docs/guides/index.ts
index ef9322446..15aaa9ec1 100644
--- a/packages/console/src/assets/docs/guides/index.ts
+++ b/packages/console/src/assets/docs/guides/index.ts
@@ -2,6 +2,7 @@
import { lazy } from 'react';
+import apiExpress from './api-express/index';
import m2mGeneral from './m2m-general/index';
import nativeAndroidJava from './native-android-java/index';
import nativeAndroidKt from './native-android-kt/index';
@@ -157,6 +158,13 @@ const guides: Readonly = Object.freeze([
Component: lazy(async () => import('./web-outline/README.mdx')),
metadata: webOutline,
},
+ {
+ order: Number.POSITIVE_INFINITY,
+ id: 'api-express',
+ Logo: lazy(async () => import('./api-express/logo.svg')),
+ Component: lazy(async () => import('./api-express/README.mdx')),
+ metadata: apiExpress,
+ },
]);
export default guides;