diff --git a/src/content/docs/authenticate/device-authorization-flow/api-calls.mdx b/src/content/docs/authenticate/device-authorization-flow/api-calls.mdx new file mode 100644 index 00000000..19bbbefd --- /dev/null +++ b/src/content/docs/authenticate/device-authorization-flow/api-calls.mdx @@ -0,0 +1,234 @@ +--- +page_id: 8d8234a5-b064-47a1-a1e1-9b3f98a57f34 +title: Call your API using device authorization flow +sidebar: + order: 3 +relatedArticles: + - 2944d2bc-4e84-4918-b4f6-7406a7c26f98 + - 888b1546-8047-4609-af59-8cf859527aa0 + - de937e16-8094-4aad-ada9-e6a37d74f508 + - 1cbd91d2-c0b3-45b3-b038-319de1b2c794 +--- + +Once you've received an access token from the device authorization flow, you can use it to call your protected APIs. This guide shows you how to validate tokens, handle scopes, and make authenticated API requests. + +## Use the access token from the device authorization flow + +The access token you receive from the device authorization flow is a standard OAuth 2.0 Bearer token. Include it in the `Authorization` header of your API requests: + +```bash +curl -X GET https://your-api.com/protected-resource \ + -H "Authorization: Bearer YOUR_ACCESS_TOKEN" +``` + +## Token validation in the device authorization flow + +Before processing API requests, validate the access token to ensure it's valid and hasn't expired: + +### Validate with Kinde's userinfo endpoint + +```bash +curl -X GET https://.kinde.com/oauth2/v2/user_profile \ + -H "Authorization: Bearer YOUR_ACCESS_TOKEN" +``` + +**Success response**: + +```json +{ + "sub": "kp_c3143a4b50ad43c88e541d9077681782", + "provided_id": "some_external_id", + "name": "John Snow", + "given_name": "John", + "family_name": "Snow", + "updated_at": 1612345678, + "email": "john.snow@example.com", + "email_verified": true, + "picture": "https://example.com/john_snow.jpg", + "preferred_username": "john_snow", + "id": "kp_c3143a4b50ad43c88e541d9077681782" +} +``` + +**Error response** (invalid token): + +```json +{ + "error": "invalid_token", + "error_description": "The access token is invalid or expired" +} +``` + +### Validate with your own API + +You can also validate tokens in your own API by verifying the JWT signature and claims: + +```javascript ++// Node.js example using jsonwebtoken with JWKS ++const jwt = require("jsonwebtoken"); ++const jwksClient = require("jwks-rsa"); ++ ++const client = jwksClient({ ++ jwksUri: "https://.kinde.com/.well-known/jwks" ++}); ++ ++function getKey(header, callback) { ++ client.getSigningKey(header.kid, (err, key) => { ++ const signingKey = key.publicKey || key.rsaPublicKey; ++ callback(null, signingKey); ++ }); ++} ++ ++function validateToken(token) { ++ return new Promise((resolve, reject) => { ++ jwt.verify(token, getKey, { algorithms: ["RS256"] }, (err, decoded) => { ++ if (err) { ++ resolve({ valid: false, error: err.message }); ++ } else { ++ resolve({ valid: true, user: decoded }); ++ } ++ }); ++ }); ++} +``` + +## Scope enforcement for device authorization + +Access tokens include scopes that determine what resources the user can access. Check the required scopes before processing requests: + +```javascript +// Example: Check if user has required scope +function hasRequiredScope(token, requiredScope) { + const decoded = jwt.decode(token); + const tokenScopes = decoded.scope.split(" "); + return tokenScopes.includes(requiredScope); +} + +// Usage +if (!hasRequiredScope(accessToken, "read:users")) { + return res.status(403).json({error: "Insufficient scope"}); +} +``` + +## Common API patterns for device authorization + +### Protected resource endpoint + +```javascript +// Express.js example +app.get("/api/protected-resource", authenticateToken, (req, res) => { + // req.user contains the decoded token payload + res.json({ + message: "Access granted", + user: req.user + }); +}); + +function authenticateToken(req, res, next) { + const authHeader = req.headers["authorization"]; + const token = authHeader && authHeader.split(" ")[1]; + + if (!token) { + return res.status(401).json({error: "Access token required"}); + } + + // Validate token with Kinde + fetch("https://.kinde.com/oauth2/v2/user_profile", { + headers: { + Authorization: `Bearer ${token}` + } + }) + .then((response) => { + if (!response.ok) { + throw new Error("Invalid token"); + } + return response.json(); + }) + .then((user) => { + req.user = user; + next(); + }) + .catch((error) => { + return res.status(401).json({error: "Invalid token"}); + }); +} +``` + +### Error handling for device authorization + +Handle common token-related errors: + +```javascript +function handleTokenError(res, error) { + switch (error.error) { + case "invalid_token": + // Token is invalid or expired + return res.status(401).json({error: "Please re-authenticate"}); + + case "insufficient_scope": + // Token doesn't have required permissions + return res.status(403).json({error: "Insufficient permissions"}); + + default: + return res.status(500).json({error: "Authentication error"}); + } +} +``` + +## Security best practices for device authorization + +### Token storage + +- **Never store tokens in localStorage**: Use secure HTTP-only cookies or memory storage +- **Validate tokens server-side**: Always validate tokens on your backend, not just the client + +### Rate limiting + +Implement rate limiting for token validation requests: + +```javascript +const rateLimit = require("express-rate-limit"); + +const tokenValidationLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // limit each IP to 100 requests per windowMs + message: "Too many token validation requests" +}); + +app.use("/api/protected-resource", tokenValidationLimiter); +``` + +### Logging and monitoring + +Log authentication events for security monitoring: + +```javascript +function logAuthEvent(token, action, success) { + console.log({ + timestamp: new Date().toISOString(), + action: action, + success: success, + userId: token.user_id, + scopes: token.scope + }); +} +``` + +## Testing your API + +Test your protected endpoints with the access token: + +```bash +# Test with curl +curl -X GET https://your-api.com/protected-resource \ + -H "Authorization: Bearer YOUR_ACCESS_TOKEN" + +# Test with JavaScript +fetch('https://your-api.com/protected-resource', { + headers: { + 'Authorization': 'Bearer YOUR_ACCESS_TOKEN' + } +}) +.then(response => response.json()) +.then(data => console.log(data)); +``` diff --git a/src/content/docs/authenticate/device-authorization-flow/overview.mdx b/src/content/docs/authenticate/device-authorization-flow/overview.mdx new file mode 100644 index 00000000..e3ed7fc7 --- /dev/null +++ b/src/content/docs/authenticate/device-authorization-flow/overview.mdx @@ -0,0 +1,119 @@ +--- +page_id: ab3834cc-4645-4b7a-826a-c7f502eee3dd +title: About the device authorization flow +sidebar: + order: 2 +relatedArticles: + - 2944d2bc-4e84-4918-b4f6-7406a7c26f98 + - 8d8234a5-b064-47a1-a1e1-9b3f98a57f34 + - 888b1546-8047-4609-af59-8cf859527aa0 + - 28c6e830-8e82-4bf8-aab7-87ebafeb68e4 +--- + +Kinde's device authorization flow adheres to `RFC 8628`, also known as the OAuth 2.0 Device Authorization Grant. It enables authorization for devices with limited input capabilities, such as smart TVs, gaming consoles, or IoT devices. Users authenticate on a secondary device (like a phone or computer) while the primary device receives the access token. + +## How the device authentication flow works + +1. **Device requests authorization**: The device requests a device code and user code from Kinde. +2. **User authenticates**: The user visits a verification URI on another device and enters the user code. +3. **Device polls for token**: The device polls the token endpoint until authorization is complete. +4. **Access granted**: The device receives an access token and can call protected APIs. + +## Endpoints for the device authorization flow + +### Device authorization endpoint + +**URL**: `https://.kinde.com/oauth2/device/auth` + +**Method**: `POST` + +**Content-Type**: `application/x-www-form-urlencoded` + +**Parameters**: + +- `client_id` (optional): Your application's client ID - can be omitted if you have set an application as the default for device flows +- `audience` (optional): The audience to use for the request + +**Response**: + +```json +{ + "device_code": "kinde_dc_device_code_here", + "user_code": "CSLDFDUU", + "verification_uri": "https://.kinde.com/device", + "verification_uri_complete": "https://.kinde.com/device?user_code=CSLDFDUU", + "expires_in": 600, + "interval": 5, + "qr_code": "data:image/png;base64,..." +} +``` + +### Token endpoint + +**URL**: `https://.kinde.com/oauth2/token` + +**Method**: `POST` + +**Content-Type**: `application/x-www-form-urlencoded` + +**Parameters**: + +- `grant_type`: `urn:ietf:params:oauth:grant-type:device_code` +- `client_id`: Your application's client ID +- `device_code`: The device code received from the authorization endpoint + +**Success response**: + +```json +{ + "access_token": "eyJ...", + "expires_in": 86400, + "scope": "", + "token_type": "bearer" +} +``` + +The scope field may be empty because granted scopes are carried in the access token’s scope claim. + +**Example error response**: + +```json +{ + "error": "authorization_pending", + "error_description": "The user has not yet completed the authorization" +} +``` + +## Polling behavior + +The device must poll the token endpoint at regular intervals until the user completes authentication: + +- **Initial interval**: Use the `interval` value from the device authorization response (typically 5 seconds). +- **Slow down**: If you receive a `slow_down` error, increase the polling interval by 5 seconds. +- **Maximum time**: Stop polling after the `expires_in` time (typically 30 minutes). + +## Device authorization flow error codes + +| Error Code | Description | Action | +| ----------------------- | ------------------------------------ | ------------------------------ | +| `authorization_pending` | User hasn't completed authentication | Continue polling | +| `slow_down` | Polling too frequently | Increase interval by 5 seconds | +| `access_denied` | User denied the authorization | Stop polling | +| `expired_token` | Device code has expired | Request a new device code | +| `server_error` | Misconfigured device code | Request a new device code | + +## Security considerations for device authorization + +- **User code format**: User codes are formatted as `XXXXXXXX` for easy entry. +- **Verification URI**: Users should verify they're on the correct domain. +- **Token expiration**: Access tokens expire after 1 hour by default. + +## Specifying an audience in a device authorization request + +If an `audience` is specified in the request, the access token will include the audience in the `aud` claim. Kinde supports requesting multiple audiences. + +The API must be authorized for the device authorization application. + +## Scopes and permissions for a device authorization request + +If an audience is specified in the request, any scopes which are belong to that audience that are granted to the user by their role will also be granted to the device. The list of scopes will be displayed on the consent screen. If the user consents, the scopes will be included in the `scope` claim of the access token. diff --git a/src/content/docs/authenticate/device-authorization-flow/quick-start.mdx b/src/content/docs/authenticate/device-authorization-flow/quick-start.mdx new file mode 100644 index 00000000..e69e3f7d --- /dev/null +++ b/src/content/docs/authenticate/device-authorization-flow/quick-start.mdx @@ -0,0 +1,104 @@ +--- +page_id: 2944d2bc-4e84-4918-b4f6-7406a7c26f98 +title: Quick start +sidebar: + order: 1 +relatedArticles: + - 8d8234a5-b064-47a1-a1e1-9b3f98a57f34 + - 888b1546-8047-4609-af59-8cf859527aa0 +--- + +The 'Device Authorization Flow' allows users to authenticate on one device (like a TV or smart device) using another device (like a phone or computer). This is perfect for devices with limited input capabilities. + +In this quick start, you'll learn how to implement the device authorization flow using Kinde in just 5 minutes. + +## Prerequisites for the device authorization flow + +- `curl` or a similar HTTP client + +## Step 1: Create a Device Authorization app + +1. From the Kinde home page select **Add application**. +2. Enter a name for the application. +3. Choose **Device and IoT**. +4. Select **Save**. +5. Make a note of the Client ID, you'll need this later. + +## Step 2: Enable an authentication method for your application + +1. Go to **Settings > Authentication**. +2. Select **Configure** on the **Passwordless** > **Email + code** card. +3. Under **Applications** select the application you created in step 1. +4. Select **Save**. + +## Step 3: Request a device code + +Request a device code from Kinde's authorization endpoint: + +```bash +curl -X POST https://.kinde.com/oauth2/device/auth \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "client_id=" +``` + +The response will include a `device_code`, `user_code`, and `verification_uri`: + +```json +{ + "device_code": "kinde_dc_...", + "user_code": "CSLDFDUU", + "verification_uri": "https://.kinde.com/device", + "verification_uri_complete": "https://.kinde.com/device?user_code=CSLDFDUU", + "expires_in": 600, + "interval": 5, + "qr_code": "data:image/png;base64,..." +} +``` + +## Step 4: Display the user code + +Show the `user_code` to the user and provide the `verification_uri_complete` or QR code from the response. The user should: + +1. Visit the `verification_uri_complete` URL on their phone or computer. +2. Complete the authentication process. + +## Step 5: Poll for the access token + +While the user is authenticating, poll the token endpoint: + +```bash +curl -X POST https://.kinde.com/oauth2/token \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \ + -d "client_id=" \ + -d "device_code=" +``` + +Continue polling every 5 seconds (or the `interval` value from the response) until you receive a successful response like: + +```json +{ + "access_token": "eyJ...", + "expires_in": 86400, + "scope": "", + "token_type": "bearer" +} +``` + +## Step 6: Use the access token + +Once you have received the access token, you can call your protected APIs: + +```bash +curl -X GET https://your-api.com/protected-resource \ + -H "Authorization: Bearer YOUR_ACCESS_TOKEN" +``` + +## Default app for device flows + +When you set up a default app for device flows, this will be the application that is used if no Client ID is specified in the request. + +1. Select **Settings** > **Applications** +2. Select the Device Authorization application you want to set as default +3. Select **Set as default** +4. Select **Save** diff --git a/src/content/docs/authenticate/device-authorization-flow/troubleshooting.mdx b/src/content/docs/authenticate/device-authorization-flow/troubleshooting.mdx new file mode 100644 index 00000000..eebe047c --- /dev/null +++ b/src/content/docs/authenticate/device-authorization-flow/troubleshooting.mdx @@ -0,0 +1,206 @@ +--- +page_id: 545c511c-ce01-4251-be2a-d04407820d6f +title: Troubleshooting device authorization +sidebar: + order: 6 +relatedArticles: + - 2944d2bc-4e84-4918-b4f6-7406a7c26f98 + - 888b1546-8047-4609-af59-8cf859527aa0 + - de937e16-8094-4aad-ada9-e6a37d74f508 +--- + +This guide helps you diagnose and resolve common issues with device authorization flow. Learn how to handle errors, debug problems, and implement proper error recovery. + +## Common error codes during device authorization + +### authorization_pending + +**Error**: `authorization_pending` +**Description**: The user hasn't completed the authorization process yet. + +**Solution**: Continue polling the token endpoint. This is normal behavior. + +```javascript +// Example: Handle authorization_pending +if (error === "authorization_pending") { + console.log("User has not completed authorization yet. Continue polling..."); + // Wait for the specified interval before next poll + setTimeout(pollForToken, interval * 1000); +} +``` + +### slow_down + +**Error**: `slow_down` +**Description**: You're polling too frequently. + +**Solution**: Increase the polling interval by 5 seconds. + +```javascript +// Example: Handle slow_down +if (error === "slow_down") { + console.log("Polling too fast. Increasing interval..."); + interval += 5; // Increase interval by 5 seconds + setTimeout(pollForToken, interval * 1000); +} +``` + +### access_denied + +**Error**: `access_denied` +**Description**: The user denied the authorization request. + +**Solution**: Stop polling and inform the user they need to try again. + +```javascript +// Example: Handle access_denied +if (error === "access_denied") { + console.log("User denied authorization"); + showErrorMessage("Authorization was denied. Please try again."); + stopPolling(); +} +``` + +### expired_token + +**Error**: `expired_token` +**Description**: The device code has expired (typically after 30 minutes). + +**Solution**: Request a new device code. + +```javascript +// Example: Handle expired_token +if (error === "expired_token") { + console.log("Device code expired"); + showErrorMessage("This code has expired. Please request a new one."); + requestNewDeviceCode(); +} +``` + +## Polling mistakes during device authorization + +### Too frequent polling + +**Problem**: Polling more frequently than the recommended interval. + +**Solution**: Always respect the `interval` value from the device authorization response. + +```javascript +// Good: Respect the interval +function pollForToken(deviceCode, interval = 5) { + setTimeout(() => { + // Make token request + checkTokenStatus(deviceCode); + }, interval * 1000); +} + +// Bad: Polling too frequently +function pollForToken(deviceCode) { + setInterval(() => { + // This polls every 1 second - too frequent! + checkTokenStatus(deviceCode); + }, 1000); +} +``` + +### Not handling `slow_down` properly + +**Problem**: Not increasing the interval when receiving `slow_down` errors. + +**Solution**: Implement exponential backoff. + +```javascript +let currentInterval = 5; // Start with 5 seconds + +function pollForToken(deviceCode) { + checkTokenStatus(deviceCode).then((response) => { + if (response.error === "slow_down") { + currentInterval += 5; // Increase by 5 seconds + console.log(`Increasing interval to ${currentInterval} seconds`); + } + + // Continue polling with updated interval + setTimeout(() => pollForToken(deviceCode), currentInterval * 1000); + }); +} +``` + +### Not stopping on errors + +**Problem**: Continuing to poll after receiving fatal errors. + +**Solution**: Stop polling for non-recoverable errors. + +```javascript +function pollForToken(deviceCode) { + checkTokenStatus(deviceCode).then((response) => { + if (response.error) { + switch (response.error) { + case "authorization_pending": + // Continue polling + setTimeout(() => pollForToken(deviceCode), interval * 1000); + break; + case "slow_down": + // Increase interval and continue + interval += 5; + setTimeout(() => pollForToken(deviceCode), interval * 1000); + break; + case "access_denied": + case "expired_token": + // Stop polling - these are fatal errors + stopPolling(); + handleError(response.error); + break; + } + } else { + // Success - stop polling + handleSuccess(response); + } + }); +} +``` + +## Network issues during device authorization + +### Connection timeouts + +**Problem**: Network requests timing out. + +**Solution**: Implement proper timeout handling and retry logic. + +```javascript +function checkTokenStatus(deviceCode) { + return fetch("https://.kinde.com/oauth2/token", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: new URLSearchParams({ + grant_type: "urn:ietf:params:oauth:grant-type:device_code", + client_id: "", + device_code: deviceCode + }), + timeout: 10000 // 10 second timeout + }) + .then((response) => response.json()) + .catch((error) => { + console.error("Network error:", error); + // Retry after a delay + setTimeout(() => checkTokenStatus(deviceCode), 5000); + }); +} +``` + +### DNS resolution issues + +**Problem**: Cannot resolve the Kinde domain. + +**Solution**: Verify your domain configuration and network connectivity. + +```bash +# Test DNS resolution +nslookup .kinde.com + +# Test connectivity +curl -I https://.kinde.com/oauth2/v2/device_authorization +``` diff --git a/src/content/docs/machine-to-machine-applications/about-m2m/m2m-quick-start-guide.mdx b/src/content/docs/machine-to-machine-applications/about-m2m/m2m-quick-start-guide.mdx index 3f9aedb3..7fff4287 100644 --- a/src/content/docs/machine-to-machine-applications/about-m2m/m2m-quick-start-guide.mdx +++ b/src/content/docs/machine-to-machine-applications/about-m2m/m2m-quick-start-guide.mdx @@ -1,6 +1,6 @@ --- page_id: 4cb63554-d6b3-45cf-bdad-2fac4790d3aa -title: Quick start +title: M2M quick start description: Step-by-step quick start guide for creating and using M2M applications in Kinde including API authorization, scope assignment, and token generation sidebar: order: 2 @@ -28,7 +28,7 @@ keywords: - access tokens - bearer token - curl -updated: 2024-01-15 +updated: 2025-08-05 featured: false deprecated: false ai_summary: Step-by-step quick start guide for creating and using M2M applications in Kinde including API authorization, scope assignment, and token generation. @@ -38,55 +38,46 @@ This guide shows you how to create a Machine-to-Machine (M2M) application in Kin ## Step 1: Create a machine-to-machine app -1. Go to the **Applications** section on the Kinde homepage and select **Add application**. -2. In the window that opens, enter a name for the application. -3. Select **Machine-to-machine (M2M)**. +1. On the Kinde home page, select **Add application**. +2. Enter a name for the application. +3. Choose **Machine-to-machine (M2M)**. 4. Select **Save**. ## Step 2: Create an API in Kinde (if you don't have one) You can skip this step if you already have an API registered in Kinde. -1. Go to **Settings > Environment > APIs**. -2. Select **Add API** -3. Enter a **Name** and **Audience**. +1. Go to **Settings > APIs**. +2. Select **Add API**. +3. Enter a name for the API. 4. Select **Save**. -For more information, see [Register and manage APIs](/developer-tools/your-apis/register-manage-apis/). +For details on the full API setup, see [Register and manage APIs](/developer-tools/your-apis/register-manage-apis/). ## Step 3: Authorize the app to access an API -You need to authorize the API or else token requests will be rejected. +1. Open the newly created M2M app. +2. Go to **APIs** in the menu. +3. Find the API you want to authorize in the list. +4. Select the three dots on the far right and select **Authorize**. -1. Open your newly created app. -2. Select **APIs** in the side menu. -3. Find the API to authorize, and select the three dots menu on the right. -4. Select **Authorize**. +## Step 4: (Optional) Add scopes to define permissions -## Step 4: Add scopes (optional) +1. Go to **Settings > APIs**. +2. Choose the API you want to protect. +3. Select **Scopes** in the menu and add scopes (e.g. `read:users`, `write:flags`). +4. Once you are finished, open the M2M app and assign the scopes. See [Define and manage API scopes](/developer-tools/your-apis/api-scopes-m2m-applications/). -Complete this step if your API uses scopes to define permissions. - -1. Go to **Settings > Environment > APIs**. -2. Choose the API you’re protecting. -3. Select **Scopes** from the menu. -4. Select **Add scope** and enter a scope name and description, then select **Save**. -5. Repeat for each scope you want to add, e.g. `read:users`, `write:flags`. -6. Go back to your M2M app and assign those scopes. - -For more information about how to assign scopes to an API, see [Define and manage API scopes](/developer-tools/your-apis/api-scopes-m2m-applications/). - -## Step 5: Get a token +## Step 5: Get a token to test your M2M app You can test the app in one of two ways: -### Option A - Use the Test tab in Kinde +### Option A - Use the **Test** details in Kinde 1. Open your M2M app. -2. Select **APIs** in the menu and then open the API you want to test. -3. Select **Test** in the menu. -4. Select the Authorized application and then **Get token**. -5. Copy the generated token. +2. Go to **Test** in the menu. +3. Select the API (audience). +4. Copy the generated token. ### Option B - Use the client credentials flow directly @@ -118,4 +109,3 @@ Include the token in the `Authorization` header: curl https://your-subdomain.kinde.com/v1/organizations \ -H "Authorization: Bearer ``` - diff --git a/src/data/sidebarData.ts b/src/data/sidebarData.ts index 4c4ebc1b..a1a0f5cc 100644 --- a/src/data/sidebarData.ts +++ b/src/data/sidebarData.ts @@ -11,7 +11,10 @@ const sidebarData = [ items: [ {label: "Guides", autogenerate: {directory: "get-started/guides"}, collapsed: false}, { - label: "Learn about Kinde", autogenerate: {directory: "get-started/learn-about-kinde"}, collapsed: false}, + label: "Learn about Kinde", + autogenerate: {directory: "get-started/learn-about-kinde"}, + collapsed: false + }, { label: "Switch to Kinde", autogenerate: {directory: "get-started/switch-to-kinde"}, @@ -137,6 +140,11 @@ const sidebarData = [ label: "Social connections", autogenerate: {directory: "authenticate/social-sign-in"}, collapsed: false + }, + { + label: "Device authorization flow", + autogenerate: {directory: "authenticate/device-authorization-flow"}, + collapsed: false } ] }, @@ -287,13 +295,21 @@ const sidebarData = [ autogenerate: {directory: "machine-to-machine-applications/organization-scoped-m2m-apps"}, collapsed: false }, - {label: "M2M token customization", autogenerate: {directory: "machine-to-machine-applications/m2m-token-customization"}, collapsed: false}, + { + label: "M2M token customization", + autogenerate: {directory: "machine-to-machine-applications/m2m-token-customization"}, + collapsed: false + }, { label: "Automation", autogenerate: {directory: "machine-to-machine-applications/m2m-workflow-automation"}, collapsed: false }, - {label: "Troubleshooting", autogenerate: {directory: "machine-to-machine-applications/troubleshooting-m2m"}, collapsed: false} + { + label: "Troubleshooting", + autogenerate: {directory: "machine-to-machine-applications/troubleshooting-m2m"}, + collapsed: false + } ] }, { @@ -336,11 +352,13 @@ const sidebarData = [ { label: "About", autogenerate: {directory: "releases/about"}, - collapsed: false}, + collapsed: false + }, { label: "Guides", autogenerate: {directory: "releases/guides"}, - collapsed: false}, + collapsed: false + }, { label: "Feature flags", autogenerate: {directory: "releases/feature-flags"},