Skip to content

Commit 8b75fc1

Browse files
authored
refactor(console): update M2M guide samples with real app metadata (#7818)
1 parent 605a64f commit 8b75fc1

File tree

6 files changed

+93
-83
lines changed

6 files changed

+93
-83
lines changed

packages/console/src/assets/docs/guides/m2m-general/README.mdx

Lines changed: 73 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ There are two common use cases of using machine-to-machine apps in Logto:
2121
1. **Accessing Logto Management API**: In this case, you need to assign a M2M role that include the `all` permission from the built-in Logto Management API to your M2M app.
2222
2. **Accessing your API resource**: In this case, you need to assign M2M roles that include permissions from your API resources to your M2M app.
2323

24-
During the M2M app creation process, youll be directed to a page where you can assign machine-to-machine (M2M) roles to your applications:
24+
During the M2M app creation process, you'll be directed to a page where you can assign machine-to-machine (M2M) roles to your applications:
2525

2626
<img alt="Assign M2M roles modal" src={AssignM2mRolesModalSrc} width="600px" style={{ borderRadius: '6px' }} />
2727

@@ -42,104 +42,101 @@ And you also need to include your M2M app's credentials in the request header fo
4242

4343
This is achieved by including the app's credentials in the [Basic authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization#basic_authentication) form in the request `Authorization` header, where username is the App ID, and password is the App Secret.
4444

45-
You can find the App ID and App Secret from your M2M app's details page:
45+
The App ID and App Secret are as follows, and you can also find them in your M2M app details page:
4646

4747
<ApplicationCredentials />
4848

49-
An example of the access token request is:
49+
And the authentication string can be generated by encoding the app ID and app secret to Base64 format, with the fake code below:
5050

51-
```
52-
POST /oidc/token HTTP/1.1
53-
Host: your.logto.endpoint
54-
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
51+
<Code className="language-js">
52+
{`base64_encode("${props.app.id}:${props.showAppSecret ? props.secrets[0].value : '*'.repeat(props.secrets[0].value.length)}")
53+
// Result: ${props.showAppSecret ? btoa(`${props.app.id}:${props.secrets[0].value}`) : '*'.repeat(btoa(`${props.app.id}:${props.secrets[0].value}`).length)}`}
54+
</Code>
55+
56+
An example of fetching the M2M access token request:
57+
58+
<Code className="language-bash">
59+
{`POST /oidc/token HTTP/1.1
60+
Host: ${props.endpoint}
61+
Authorization: Basic ${props.showAppSecret ? btoa(`${props.app.id}:${props.secrets[0].value}`) : '*'.repeat(btoa(`${props.app.id}:${props.secrets[0].value}`).length)}
5562
Content-Type: application/x-www-form-urlencoded
5663
5764
grant_type=client_credentials
58-
resource=https://shopping.api
59-
scope=read:products write:products
60-
```
65+
resource=https://${props.app.tenantId}.logto.app/api
66+
scope=all`}
67+
</Code>
6168

6269
</Step>
6370
<Step title="Request an access token">
6471

65-
<InlineNotification>
66-
In the following demonstration, replace `https://your.logto.endpoint` with the Logto endpoint you are targeting. For Logto Cloud, it will be `https://[your-tenant-id].logto.app`.
67-
</InlineNotification>
68-
6972
<Tabs>
7073

7174
<TabItem value="Logto Management API" label="For Logto Management API">
7275

73-
Logto provides a built-in Logto Management API resource, its a readonly resource with the `all` permission to access Logto Management API, you can see it from your API resource list.
74-
The resource API indicator is in the pattern of `https://[your-tenant-id].logto.app/api` , and this will be your resource value used in the access token request body.
76+
Logto provides a built-in "Logto Management API" resource, it's a readonly resource with the `all` permission to access Logto Management API, you can see it from your API resource list.
77+
The resource API indicator is in the pattern of `https://[your-tenant-id].logto.app/api`, and this will be your resource value used in the access token request body.
7578

7679
<img alt="Logto Management API details" src={LogtoManagementApiSrc} width="600px" style={{ borderRadius: '6px' }}/>
7780

78-
Before accessing Logto Management API, make sure your M2M app has been assigned with M2M roles that include the `all` permission from this built-in Logto Management API resource.
81+
Before accessing Logto Management API, make sure your M2M app has been assigned with M2M roles that include the `all` permission from this built-in "Logto Management API" resource.
7982

8083
<InlineNotification>
81-
Logto also provides a pre-configured Logto Management API access M2M role for new created tenants, which the Logto Management API resources all permission has already assigned to. You can use it directly without manually setting permissions. This pre-configured role can also be edited and deleted as needed.
84+
Logto also provides a pre-configured "Logto Management API access" M2M role for new created tenants, which the Logto Management API resource's all permission has already assigned to. You can use it directly without manually setting permissions. This pre-configured role can also be edited and deleted as needed.
8285
</InlineNotification>
8386

8487
Now, compose all we have and send the request:
8588

8689
<Tabs>
8790
<TabItem value="Node.js" label="Node.js">
8891

89-
```js
90-
const logtoEndpoint = 'https://your.logto.endpoint'; // Replace with your Logto endpoint
91-
const tokenEndpoint = `${logtoEndpoint}/oidc/token`;
92-
const applicationId = 'your-application-id';
93-
const applicationSecret = 'your-application-secret';
94-
const tenantId = 'your-tenant-id';
92+
<Code className="language-js">
93+
{`const logtoEndpoint = '${props.endpoint}';
94+
const tokenEndpoint = '${new URL('/oidc/token', props.endpoint).href}';
95+
const appId = '${props.app.id}';
96+
const appSecret = '${props.showAppSecret ? props.secrets[0].value : '*'.repeat(props.secrets[0].value.length)}';
9597
9698
const fetchAccessToken = async () => {
9799
return await fetch(tokenEndpoint, {
98100
method: 'POST',
99101
headers: {
100102
'Content-Type': 'application/x-www-form-urlencoded',
101-
Authorization: `Basic ${Buffer.from(`${applicationId}:${applicationSecret}`).toString(
102-
'base64'
103-
)}`,
103+
Authorization: \`Basic \${Buffer.from(\`\${appId}:\${appSecret}\`).toString('base64')}\`,
104+
)}\`,
104105
},
105106
body: new URLSearchParams({
106107
grant_type: 'client_credentials',
107-
resource: `https://${tenantId}.logto.app/api`,
108+
resource: 'https://${props.app.tenantId}.logto.app/api',
108109
scope: 'all',
109110
}).toString(),
110111
});
111-
};
112-
```
112+
};`}
113+
</Code>
113114

114115
</TabItem>
115116

116117
<TabItem value="cURL" label="cURL">
117118

118-
```bash
119-
curl --location \
120-
--request POST 'https://your.logto.endpoint' \ # Replace with your Logto endpoint
121-
--header 'Authorization: Basic ${your_auth_string}' \
122-
--header 'Content-Type: application/x-www-form-urlencoded' \
123-
--data-urlencode 'grant_type=client_credentials' \
124-
--data-urlencode 'resource=https://${tenantId}.logto.app/api' \
125-
--data-urlencode 'scope=all'
126-
```
119+
<Code className="language-bash">
120+
{`curl --location \\
121+
--request POST '${new URL('/oidc/token', props.endpoint).href}' \\
122+
--header 'Authorization: Basic ${props.showAppSecret ? btoa(`${props.app.id}:${props.secrets[0].value}`) : '*'.repeat(btoa(`${props.app.id}:${props.secrets[0].value}`).length)}' \\
123+
--header 'Content-Type: application/x-www-form-urlencoded' \\
124+
--data-urlencode 'grant_type=client_credentials' \\
125+
--data-urlencode 'resource=https://${props.app.tenantId}.logto.app/api' \\
126+
--data-urlencode 'scope=all'`}
127+
</Code>
127128

128129
</TabItem>
129130

130131
</Tabs>
131132

132-
<InlineNotification>
133-
For Logto Cloud users: when you’re interacting with Logto Management API, you can not use custom domain, use the default Logto endpoint `https://[your_tenant_id].logto.app/oidc/token` to grant access tokens.
134-
</InlineNotification>
135-
136133
### Access token response
137134

138135
A successful access token response body would be like:
139136

140137
```json
141138
{
142-
"access_token": "<granted-access-token>", // Use this token to access the API resource
139+
"access_token": "<granted-access-token>", // E.g. eyJhb...2g
143140
"expires_in": 3600, // Token expiration in seconds
144141
"token_type": "Bearer", // Auth type for your request when using the access token
145142
"scope": "all" // scope `all` for Logto Management API
@@ -158,7 +155,7 @@ In your API Resource list, find the API identifier that the app needs to access.
158155

159156
<img alt="API identifier" src={AppIdentifierSrc} width="600px" style={{ borderRadius: '6px' }} />
160157

161-
Assume that we have `read:products` and `write:products` permissions under this Online Shopping API resource.
158+
Assume that we have `read:products` and `write:products` permissions under this "Online Shopping" API resource.
162159

163160
Before accessing your API resource, make sure your M2M app has been assigned with M2M roles that include permissions from your API resource.
164161

@@ -168,43 +165,41 @@ Now, compose all we have and send the request:
168165

169166
<TabItem value="Node.js" label="Node.js">
170167

171-
```js
172-
const logtoEndpoint = 'https://your.logto.endpoint';
173-
const tokenEndpoint = `${logtoEndpoint}/oidc/token`;
174-
const applicationId = 'your-application-id';
175-
const applicationSecret = 'your-application-secret';
168+
<Code className="language-js">
169+
{`const logtoEndpoint = '${props.endpoint}';
170+
const tokenEndpoint = '${new URL('/oidc/token', props.endpoint).href}';
171+
const appId = '${props.app.id}';
172+
const appSecret = '${props.showAppSecret ? props.secrets[0].value : '*'.repeat(props.secrets[0].value.length)}';
176173
177174
const fetchAccessToken = async () => {
178175
return await fetch(tokenEndpoint, {
179176
method: 'POST',
180177
headers: {
181178
'Content-Type': 'application/x-www-form-urlencoded',
182-
Authorization: `Basic ${Buffer.from(`${applicationId}:${applicationSecret}`).toString(
183-
'base64'
184-
)}`,
179+
Authorization: \`Basic \${Buffer.from(\`\${appId}:\${appSecret}\`).toString('base64')}\`,
185180
},
186181
body: new URLSearchParams({
187182
grant_type: 'client_credentials',
188183
resource: 'https://shopping.api',
189184
scope: 'read:products write:products',
190185
}).toString(),
191186
});
192-
};
193-
```
187+
};`}
188+
</Code>
194189

195190
</TabItem>
196191

197192
<TabItem value="cURL" label="cURL">
198193

199-
```bash
200-
curl --location \
201-
--request POST 'https://your.logto.endpoint/oidc/token' \
202-
--header 'Authorization: Basic ${your_auth_string}' \
203-
--header 'Content-Type: application/x-www-form-urlencoded' \
204-
--data-urlencode 'grant_type=client_credentials' \
205-
--data-urlencode 'resource=https://shopping.api' \
206-
--data-urlencode 'scope=read:products write:products'
207-
```
194+
<Code className="language-bash">
195+
{`curl --location \\
196+
--request POST '${new URL('/oidc/token', props.endpoint).href}' \\
197+
--header 'Authorization: Basic ${props.showAppSecret ? btoa(`${props.app.id}:${props.secrets[0].value}`) : '*'.repeat(btoa(`${props.app.id}:${props.secrets[0].value}`).length)}' \\
198+
--header 'Content-Type: application/x-www-form-urlencoded' \\
199+
--data-urlencode 'grant_type=client_credentials' \\
200+
--data-urlencode 'resource=https://shopping.api' \\
201+
--data-urlencode 'scope=read:products write:products'`}
202+
</Code>
208203

209204
</TabItem>
210205

@@ -235,34 +230,34 @@ You may notice the token response has a `token_type` field, which it's fixed to
235230
<Tabs>
236231
<TabItem value="Logto Management API" label="Interact with Logto Management API">
237232

238-
Using the requested access token with the built-in Logto Management API resource `https://[your-tenant-id].logto.app/api` to get all applications in Logto:
233+
Using the requested access token with the built-in Logto Management API resource `https://[your-tenant-id].logto.app/api` to get all applications in your Logto tenant:
239234

240235
<Tabs>
241236
<TabItem value="Node.js" label="Node.js">
242237

243-
```js
244-
const logtoEndpoint = 'https://your.logto.endpoint'; // Replace with your Logto endpoint
245-
const accessToken = 'eyJhb...2g'; // Access Token
238+
<Code className="language-js">
239+
{`const logtoEndpoint = '${props.endpoint}';
240+
const accessToken = 'eyJhb...2g'; // Your JWT access token
246241
247242
const fetchLogtoApplications = async () => {
248-
return await fetch(`${logtoEndpoint}/api/applications`, {
243+
return await fetch('${new URL('/api/applications', props.endpoint).href}', {
249244
method: 'GET',
250245
headers: {
251-
Authorization: `Bearer ${accessToken}`,
246+
Authorization: \`Bearer \${accessToken}\`,
252247
},
253248
});
254-
};
255-
```
249+
};`}
250+
</Code>
256251

257252
</TabItem>
258253

259254
<TabItem value="cURL" label="cURL">
260255

261-
```bash
262-
curl --location \
263-
--request GET 'https://your.logto.endpoint/api/applications' \ # Replace with your Logto endpoint
264-
--header 'Authorization: Bearer eyJhbG...2g' # Access Token
265-
```
256+
<Code className="language-bash">
257+
{`curl --location \\
258+
--request GET '${new URL('/api/applications', props.endpoint).href}' \\
259+
--header 'Authorization: Bearer eyJhbG...2g' # Access Token`}
260+
</Code>
266261

267262
</TabItem>
268263

@@ -298,7 +293,7 @@ const fetchProducts = async () => {
298293
```bash
299294
curl --location \
300295
--request GET 'https://your.api.endpoint/products' \
301-
--header 'Authorization: Bearer eyJhbG...2 # Access Token
296+
--header 'Authorization: Bearer eyJhbG...2 # Access token
302297
```
303298
304299
</TabItem>

packages/console/src/components/Guide/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type ApplicationResponse } from '@logto/schemas';
2+
import { noop } from '@silverhand/essentials';
23
import classNames from 'classnames';
34
import {
45
type ComponentType,
@@ -32,6 +33,8 @@ export type GuideContextType = {
3233
redirectUris?: string[];
3334
postLogoutRedirectUris?: string[];
3435
audience?: string;
36+
showAppSecret: boolean;
37+
setShowAppSecret: (show: boolean) => void;
3538
};
3639

3740
type Props = {
@@ -55,6 +58,8 @@ export const GuideContext = createContext<GuideContextType>({
5558
postLogoutRedirectUris: [],
5659
isCompact: false,
5760
audience: '',
61+
showAppSecret: false,
62+
setShowAppSecret: noop,
5863
});
5964

6065
function Guide({ className, guideId, isEmpty, isLoading, onClose }: Props) {

packages/console/src/ds-components/CopyToClipboard/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Props = {
3232
readonly size?: 'default' | 'small';
3333
readonly displayType?: 'block' | 'inline';
3434
readonly isWordWrapAllowed?: boolean;
35+
readonly onToggleVisibility?: (isVisible: boolean) => void;
3536
};
3637

3738
type CopyState = TFuncKey<'translation', 'admin_console.general'>;
@@ -47,6 +48,7 @@ function CopyToClipboard(
4748
size = 'default',
4849
isWordWrapAllowed = false,
4950
displayType = 'inline',
51+
onToggleVisibility,
5052
}: Props,
5153
ref: ForwardedRef<HTMLDivElement>
5254
) {
@@ -81,6 +83,7 @@ function CopyToClipboard(
8183

8284
const toggleHiddenContent = () => {
8385
setShowHiddenContent((previous) => !previous);
86+
onToggleVisibility?.(!showHiddenContent);
8487
};
8588

8689
return (

packages/console/src/mdx-components/ApplicationCredentials/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import TextLink from '@/ds-components/TextLink';
99
import styles from './index.module.scss';
1010

1111
function ApplicationCredentials() {
12-
const { app, secrets } = useContext(GuideContext);
12+
const { app, secrets, setShowAppSecret } = useContext(GuideContext);
1313
const { id } = app ?? {};
1414
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
1515

@@ -44,6 +44,7 @@ function ApplicationCredentials() {
4444
displayType="block"
4545
value={secrets[0].value}
4646
variant="border"
47+
onToggleVisibility={setShowAppSecret}
4748
/>
4849
</FormField>
4950
)}

packages/console/src/pages/ApiResourceDetails/components/ApiGuide/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type Resource } from '@logto/schemas';
22
import { conditional } from '@silverhand/essentials';
3-
import { useContext, useMemo } from 'react';
3+
import { useContext, useMemo, useState } from 'react';
44

55
import { guides } from '@/assets/docs/guides';
66
import Guide, { GuideContext, type GuideContextType } from '@/components/Guide';
@@ -16,6 +16,7 @@ type Props = {
1616

1717
function ApiGuide({ className, guideId, apiResource, isCompact, onClose }: Props) {
1818
const { tenantEndpoint } = useContext(AppDataContext);
19+
const [showAppSecret, setShowAppSecret] = useState(false);
1920

2021
const guide = guides.find(({ id }) => id === guideId);
2122

@@ -29,9 +30,11 @@ function ApiGuide({ className, guideId, apiResource, isCompact, onClose }: Props
2930
isCompact: Boolean(isCompact),
3031
endpoint: tenantEndpoint?.href ?? '',
3132
audience: apiResource.indicator,
33+
showAppSecret,
34+
setShowAppSecret,
3235
}
3336
) satisfies GuideContextType | undefined,
34-
[apiResource, guide, isCompact, tenantEndpoint?.href]
37+
[apiResource, guide, isCompact, showAppSecret, tenantEndpoint?.href]
3538
);
3639

3740
return memorizedContext ? (

0 commit comments

Comments
 (0)