0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

feat(console): add api resource metadata and guide for node express (#4455)

This commit is contained in:
Charles Zhao 2023-09-11 12:40:04 +08:00 committed by GitHub
parent 4675ad4eb4
commit e991f8d516
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 154 additions and 0 deletions

View file

@ -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';
<Steps>
<Step title="Extract the Bearer Token from request header">
A authorized request should contain an Authorization header with Bearer `<access_token>` 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);
};
```
</Step>
<Step title="Token validation" subtitle="3 steps">
<InlineNotification>
For demonstration, we use <a href="https://github.com/panva/jose" target="_blank" rel="noopener noreferrer">jose</a> package to validate the token's signature, expiration status, and required claims.
</InlineNotification>
### Install `jose` as dependency
<Tabs>
<TabItem value="npm" label="npm">
```bash
npm install jose
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add jose
```
</TabItem>
<TabItem value="pnpm" label="pnpm">
```bash
pnpm add jose
```
</TabItem>
</Tabs>
### 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://<your-logto-domain>/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.
<InlineNotification>
For <a href="https://docs.logto.io/docs/recipes/rbac/" target="_blank" rel="noopener">🔐 RBAC</a>, scope validation is also required.
</InlineNotification>
```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://<your-logto-domain>/oidc/jwks'),
{
// Expected issuer of the token, should be issued by the Logto server
issuer: 'https://<your-logto-domain>/oidc',
// Expected audience token, should be the resource indicator of the current API
audience: '<your request listener resource indicator>',
}
);
// If you are using RBAC
assert(payload.scope.includes('some_scope'));
// Custom payload logic
userId = payload.sub;
return next();
};
```
</Step>
<Step title="Apply middleware to your API">
```ts
import { verifyAuthFromRequest } from '/middleware/auth-middleware.ts';
app.get('/user/:id', verifyAuthFromRequest, (req, res, next) => {
// Custom code
});
```
</Step>
</Steps>

View file

@ -0,0 +1,9 @@
import { type GuideMetadata } from '../types';
const metadata: Readonly<GuideMetadata> = Object.freeze({
name: 'Express',
description: 'Protect your API on node (Express)',
target: 'API',
});
export default metadata;

View file

@ -0,0 +1,3 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M40 32.7951C38.836 33.0911 38.116 32.8081 37.47 31.8381L32.876 25.4821L32.212 24.6021L26.847 31.8591C26.234 32.7321 25.591 33.1121 24.447 32.8031L31.317 23.5811L24.921 15.2511C26.021 15.0371 26.781 15.1461 27.456 16.1311L32.221 22.5661L37.021 16.1661C37.636 15.2931 38.297 14.9611 39.401 15.2831L36.921 18.5711L33.561 22.9461C33.161 23.4461 33.216 23.7881 33.584 24.2711L40 32.7951ZM8.008 23.4271L8.57 20.6631C10.1 15.1931 16.37 12.9201 20.694 16.3001C23.221 18.2881 23.849 21.1001 23.724 24.2501H9.48C9.266 29.9201 13.347 33.3421 18.55 31.5961C20.375 30.9831 21.45 29.5541 21.988 27.7661C22.261 26.8701 22.713 26.7301 23.555 26.9861C23.125 29.2221 22.155 31.0901 20.105 32.2591C17.042 34.0091 12.67 33.4431 10.37 31.0111C9 29.6001 8.434 27.8121 8.18 25.9001C8.14 25.5841 8.06 25.2831 8 24.9801C8.00533 24.4628 8.008 23.9455 8.008 23.4281V23.4271ZM9.506 23.0471H22.378C22.294 18.9471 19.741 16.0351 16.252 16.0101C12.422 15.9801 9.672 18.8231 9.506 23.0471Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

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