-
Notifications
You must be signed in to change notification settings - Fork 35
Device authorization flow #515
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds a new Device Authorization Flow docs section (overview, quick start, API calls, troubleshooting), updates the M2M quick start content, and adds a "Device authorization flow" item to the docs sidebar; mostly new MDX content and sidebar formatting changes. Changes
Sequence Diagram(s)sequenceDiagram
participant Device
participant User
participant KindeAuth
participant API
Device->>KindeAuth: POST /device_authorization (client_id, scope[, audience])
KindeAuth-->>Device: device_code, user_code, verification_uri(_complete), expires_in, interval
Device->>User: Display user_code + verification URI/QR
User->>KindeAuth: Authenticate and consent via verification URI
Device->>KindeAuth: Poll /token with device_code
alt authorization_pending / slow_down
KindeAuth-->>Device: authorization_pending / slow_down
Device->>KindeAuth: Continue / backoff polling
else access_token issued
KindeAuth-->>Device: access_token (scope, aud, exp)
Device->>API: Request with Authorization: Bearer access_token
API->>KindeAuth: Validate token (userinfo or JWKS)
KindeAuth-->>API: Validation result / claims
API-->>Device: 200 OK or 401/403
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
Deploying kinde-docs-previews with
|
Latest commit: |
4877d6b
|
Status: | ✅ Deploy successful! |
Preview URL: | https://e99a5c5a.kinde-docs-previews.pages.dev |
Branch Preview URL: | https://feat-device-flow-docs.kinde-docs-previews.pages.dev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/content/docs/authenticate/device-authorization-flow/overview.mdx (1)
118-121
: Grammar / clarity nit
"any scopes which are belong to that audience"
→"any scopes that belong to that audience"
.-If an audience is specified in the request, any scopes which are belong to that audience +If an audience is specified in the request, any scopes that belong to that audience
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
(1 hunks)src/content/docs/authenticate/device-authorization-flow/overview.mdx
(1 hunks)src/content/docs/authenticate/device-authorization-flow/quick-start.mdx
(1 hunks)src/content/docs/authenticate/device-authorization-flow/troubleshooting.mdx
(1 hunks)src/content/docs/machine-to-machine-applications/about-m2m/m2m-quick-start-guide.mdx
(1 hunks)src/data/sidebarData.ts
(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: the kinde widget content properties use camelcase naming convention (e.g., `pagetitle` instead of `p...
Learnt from: DaveOrDead
PR: kinde-oss/documentation#364
File: src/content/docs/design/design-with-code/understand-page-design.mdx:42-42
Timestamp: 2025-03-19T03:59:26.373Z
Learning: The Kinde widget content properties use camelCase naming convention (e.g., `pageTitle` instead of `page_title`) in JSX code examples and documentation.
Applied to files:
src/data/sidebarData.ts
🪛 Gitleaks (8.27.2)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
20-21: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
31-32: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
213-215: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
src/content/docs/authenticate/device-authorization-flow/quick-start.mdx
94-95: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
🔇 Additional comments (28)
src/data/sidebarData.ts (4)
14-17
: LGTM! Improved formatting for better readability.The conversion from single-line to multi-line format for the "Learn about Kinde" item improves code readability and maintainability.
139-143
: Device Authorization Flow section properly integrated.The new sidebar entry is well-positioned under the "Auth and access" section with appropriate configuration. The directory path follows the expected pattern and
collapsed: false
ensures the content is accessible by default.
293-297
: Consistent formatting improvements in M2M section.The multi-line formatting for "M2M token customization" and "Troubleshooting" items maintains consistency with the overall formatting improvements in this file.
Also applies to: 303-307
349-351
: Formatting consistency maintained in Features section.The multi-line formatting for "About" and "Guides" items aligns with the overall formatting improvements throughout the file.
Also applies to: 354-356
src/content/docs/machine-to-machine-applications/about-m2m/m2m-quick-start-guide.mdx (1)
16-20
: Updated navigation flow improves clarity.The revised M2M application creation process with explicit application type selection (
Machine-to-machine (M2M)
) is clearer than the previous flow. The step-by-step approach reduces ambiguity about where to find the feature.src/content/docs/authenticate/device-authorization-flow/quick-start.mdx (6)
1-26
: Well-structured quick start guide with clear prerequisites.The document provides a comprehensive introduction to Device Authorization Flow with appropriate prerequisites and step-by-step application setup. The choice of "Device and IoT" application type aligns with the OAuth 2.0 Device Authorization Grant use case.
27-33
: Authentication method setup is correctly documented.The email + code passwordless authentication is an appropriate choice for device authorization flows, as it provides a user-friendly experience on secondary devices.
38-57
: Device authorization request follows RFC 8628 correctly.The device authorization request format is compliant with OAuth 2.0 Device Authorization Grant (RFC 8628). The response includes all required fields:
device_code
,user_code
,verification_uri
,expires_in
, andinterval
. The addition ofverification_uri_complete
andqr_code
enhances user experience.
70-87
: Token polling implementation follows best practices.The polling mechanism correctly implements the device authorization flow with proper grant type (
urn:ietf:params:oauth:grant-type:device_code
) and respects the specified interval. The success response format matches OAuth 2.0 standards.
94-96
: API usage example is appropriate but uses placeholder values.The curl example correctly demonstrates Bearer token usage for API calls. The placeholder
YOUR_ACCESS_TOKEN
is appropriate for documentation purposes and doesn't represent a security concern.
100-105
: Useful default application configuration tip.The tip about setting a default application for device flows is valuable for improving user experience when no Client ID is specified in requests.
src/content/docs/authenticate/device-authorization-flow/troubleshooting.mdx (7)
16-30
: Correct handling of authorization_pending error.The explanation and code example properly demonstrate how to handle the
authorization_pending
error, which is the expected response during the normal flow while the user completes authorization.
32-46
: Proper slow_down error handling with backoff.The implementation correctly handles the
slow_down
error by increasing the polling interval by 5 seconds, which follows RFC 8628 recommendations for rate limiting.
48-78
: Appropriate handling of terminal error conditions.Both
access_denied
andexpired_token
errors are correctly identified as terminal conditions that require stopping the polling process. The suggested actions (inform user to retry, request new device code) are appropriate.
88-104
: Excellent demonstration of proper vs improper polling.The side-by-side comparison clearly shows the difference between respecting the polling interval and polling too frequently. This helps developers avoid common implementation mistakes.
134-161
: Comprehensive error handling strategy.The switch statement approach correctly categorizes errors into recoverable (authorization_pending, slow_down) and terminal (access_denied, expired_token) conditions, implementing appropriate responses for each.
171-192
: Practical network error handling with retry logic.The timeout handling and retry logic implementation provides robust network error recovery while avoiding infinite retry loops.
200-206
: Useful network diagnostics commands.The provided bash commands (
nslookup
,curl -I
) are practical tools for diagnosing DNS and connectivity issues that developers might encounter.src/content/docs/authenticate/device-authorization-flow/api-calls.mdx (8)
17-22
: Standard OAuth 2.0 Bearer token usage.The Bearer token authentication example correctly demonstrates the standard HTTP Authorization header format for OAuth 2.0 access tokens.
28-60
: Proper token validation using Kinde's userinfo endpoint.The validation approach using Kinde's
/oauth2/v2/user_profile
endpoint is secure and follows OAuth 2.0 standards. The response examples clearly show both success and error scenarios.
90-102
: Scope enforcement logic is correctly implemented.The scope checking function properly decodes the JWT token and validates required scopes against the token's scope claim.
108-146
: Well-structured middleware for API protection.The Express.js middleware example demonstrates proper token extraction, validation, and error handling. Using Kinde's userinfo endpoint for validation is a secure approach.
173-175
: Excellent security guidance on token storage.The advice against localStorage and emphasis on server-side validation reflects current security best practices for token handling.
180-190
: Appropriate rate limiting for token validation.The rate limiting configuration (100 requests per 15 minutes) provides reasonable protection against abuse while allowing normal usage patterns.
196-206
: Comprehensive authentication event logging.The logging function captures essential security-relevant information (timestamp, action, success status, user ID, scopes) for monitoring and audit purposes.
212-225
: Practical testing examples for API endpoints.Both curl and JavaScript fetch examples correctly demonstrate how to test protected endpoints with Bearer tokens. The placeholder tokens are appropriate for documentation.
src/content/docs/authenticate/device-authorization-flow/overview.mdx (2)
98-105
: Double-check non-standardserver_error
mappingRFC 8628 does not list
server_error
for token polling; typical errors areauthorization_pending
,slow_down
,access_denied
, andexpired_token
.
Ifserver_error
is proprietary, add a note clarifying when it appears, otherwise align with the spec or remove to avoid confusion.
1-12
: Front-matter looks correctFront-matter keys (
page_id
,title
,sidebar
,relatedArticles
) follow the repo’s pattern. No concerns here.
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
Outdated
Show resolved
Hide resolved
src/content/docs/authenticate/device-authorization-flow/overview.mdx
Outdated
Show resolved
Hide resolved
src/content/docs/authenticate/device-authorization-flow/overview.mdx
Outdated
Show resolved
Hide resolved
src/content/docs/authenticate/device-authorization-flow/quick-start.mdx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/content/docs/authenticate/device-authorization-flow/quick-start.mdx (1)
3-3
: Capitalize “Quick Start” for consistencyHeadings elsewhere in the docs capitalize both words (“Quick Start”). Aligning the front-matter title improves searchability and visual consistency.
-title: Quick start +title: Quick Start
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/content/docs/authenticate/device-authorization-flow/quick-start.mdx
(1 hunks)
🧰 Additional context used
🪛 Gitleaks (8.27.2)
src/content/docs/authenticate/device-authorization-flow/quick-start.mdx
94-95: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
🔇 Additional comments (1)
src/content/docs/authenticate/device-authorization-flow/quick-start.mdx (1)
94-95
: Static-analysis false positive – placeholder token is safeGitleaks flagged the
Authorization: Bearer YOUR_ACCESS_TOKEN
header as a secret.
Because the value is a placeholder, no action is required. Consider addingYOUR_ACCESS_TOKEN
to the secret-scanner allow-list to prevent future noise.
src/content/docs/authenticate/device-authorization-flow/quick-start.mdx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/content/docs/authenticate/device-authorization-flow/quick-start.mdx (1)
38-43
: Remove non-standardgrant_type
from device-code request
grant_type=urn:ietf:params:oauth:grant-type:device_code
is not part of RFC 8628 for the device-code request and some servers will reject it. Onlyclient_id
(and optionallyscope
) should be posted here.-d "client_id=<YOUR_CLIENT_ID>" \ - -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" + # -d "scope=<YOUR_SCOPES>" # optionalsrc/content/docs/authenticate/device-authorization-flow/api-calls.mdx (1)
66-83
: JWT validation still uses shared secretThe sample still verifies the access token with
YOUR_JWT_SECRET
. Kinde signs tokens with asymmetric keys, so verification must use the public key from the JWKS endpoint (or introspection). Please update as previously suggested.
🧹 Nitpick comments (1)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx (1)
118-146
: Avoid remote validation on every request – cache JWKS / token claimsCalling
fetch(...)
for each API hit adds latency and couples API uptime to Kinde’s availability. Prefer:
- Verify the JWT locally with cached JWKS keys (rotate periodically).
- Fall back to
/userinfo
only on signature failure.This reduces network hops and increases resilience.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
(1 hunks)src/content/docs/authenticate/device-authorization-flow/quick-start.mdx
(1 hunks)src/content/docs/authenticate/device-authorization-flow/troubleshooting.mdx
(1 hunks)src/content/docs/machine-to-machine-applications/about-m2m/m2m-quick-start-guide.mdx
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/content/docs/authenticate/device-authorization-flow/troubleshooting.mdx
- src/content/docs/machine-to-machine-applications/about-m2m/m2m-quick-start-guide.mdx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: in the project documentation, the `/kinde-apis/management` and `/kinde-apis/frontend` pages use a th...
Learnt from: marcosmartini
PR: kinde-oss/documentation#253
File: src/content/docs/properties/work-with-properties/property-groups.mdx:13-13
Timestamp: 2024-11-12T06:00:08.396Z
Learning: In the project documentation, the `/kinde-apis/management` and `/kinde-apis/frontend` pages use a third-party API reference and client that loads a Single Page Application (SPA). Therefore, links to sections within these pages are valid, even if they appear to point to non-existent sections in static analysis, and should not be flagged.
Applied to files:
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
📚 Learning: the kinde documentation's base url is not `kinde.com`, so links to the documentation should use the ...
Learnt from: marcosmartini
PR: kinde-oss/documentation#226
File: src/content/docs/developer-tools/sdks/backend/nextjs-sdk.mdx:0-0
Timestamp: 2024-10-22T06:12:51.559Z
Learning: The Kinde documentation's base URL is not `kinde.com`, so links to the documentation should use the correct base URL.
Applied to files:
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
📚 Learning: the kinde widget content properties use camelcase naming convention (e.g., `pagetitle` instead of `p...
Learnt from: DaveOrDead
PR: kinde-oss/documentation#364
File: src/content/docs/design/design-with-code/understand-page-design.mdx:42-42
Timestamp: 2025-03-19T03:59:26.373Z
Learning: The Kinde widget content properties use camelCase naming convention (e.g., `pageTitle` instead of `page_title`) in JSX code examples and documentation.
Applied to files:
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
🪛 Gitleaks (8.27.2)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
20-21: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
31-32: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
213-215: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
src/content/docs/authenticate/device-authorization-flow/quick-start.mdx
20-21: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
31-32: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
🔇 Additional comments (1)
src/content/docs/authenticate/device-authorization-flow/quick-start.mdx (1)
70-75
: LGTM – correct token polling parameters
grant_type=device_code
belongs on the token endpoint, and the required parameters are present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx (1)
100-111
: Do not use jwt.decode for scope checks; verify signature firstDecode bypasses signature verification and is unsafe for authorization checks. Verify the token before inspecting claims, or pass already-verified claims.
Option A: Verify inside the function (async):
-// 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); -} +// Example: Check if user has required scope (after signature verification) +async function hasRequiredScope(token, requiredScope, getKey) { + const verifyOptions = { + algorithms: ["RS256"], + issuer: "https://<your-subdomain>.kinde.com", + audience: "<your-api-audience>" + }; + const decoded = await new Promise((resolve, reject) => { + jwt.verify(token, getKey, verifyOptions, (err, d) => err ? reject(err) : resolve(d)); + }); + const tokenScopes = (decoded.scope || "").split(" "); + return tokenScopes.includes(requiredScope); +} -// Usage -if (!hasRequiredScope(accessToken, "read:users")) { - return res.status(403).json({error: "Insufficient scope"}); -} +// Usage +if (!(await hasRequiredScope(accessToken, "read:users", getKey))) { + return res.status(403).json({ error: "Insufficient scope" }); +}Option B: Verify once in middleware and check scopes from verified claims:
function hasRequiredScopeFromClaims(claims, requiredScope) { const scopes = (claims.scope || "").split(" "); return scopes.includes(requiredScope); }src/content/docs/authenticate/device-authorization-flow/overview.mdx (1)
32-36
: Align device authorization request parameters with RFC 8628 (client_id required; add scope; remove note about omitting client_id)Per RFC 8628, device authorization request requires client_id and optionally scope. Remove the “can be omitted” note.
-**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 +**Parameters**: + +- `client_id` (required): Your application's client ID +- `scope` (optional): Space-delimited list of scopes to request +- `audience` (optional): The audience to use for the request
🧹 Nitpick comments (6)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx (3)
127-155
: Prefer local JWT verification over per-request user_profile callsCalling user_profile on every request adds latency, couples availability to the IdP, and complicates scope checks. Prefer validating the JWT locally with JWKS and issuer/audience checks, then attach claims to req.user. If you keep this pattern:
- Add a timeout and handle network errors distinctly (e.g., 502 on upstream failure).
- Consider caching positive validations briefly to reduce load.
- Perform scope checks against the token’s scope claim, not the profile payload.
Would you like a concrete Express middleware example using the validateToken/getKey above that sets req.user = decoded?
206-214
: Avoid logging sensitive data; fix claim namesAccess tokens use sub for subject; user_id may not exist. Also avoid logging raw tokens or PII.
-function logAuthEvent(token, action, success) { +function logAuthEvent(claims, action, success) { console.log({ timestamp: new Date().toISOString(), action: action, success: success, - userId: token.user_id, - scopes: token.scope + userId: claims.sub, + scopes: claims.scope }); }Consider sending to a proper audit sink and redacting as needed.
19-22
: Avoid leaking real tokens in examplesExamples use placeholders, which is fine. Add a short note below each example reminding not to paste real access tokens into shared logs or screenshots.
Also applies to: 221-234
src/content/docs/authenticate/device-authorization-flow/overview.mdx (3)
15-21
: Consistency: use “authorization” (not “authentication”)Title says device authorization; update section heading to match.
-## How the device authentication flow works +## How the device authorization flow works
97-104
: Clarify server_error descriptionserver_error is a generic upstream failure; current description suggests misconfigured device code.
-| `server_error` | Misconfigured device code | Request a new device code | +| `server_error` | Authorization server encountered an error | Back off and retry; contact support if persistent |
119-120
: Grammar and clarity fix for scopes paragraph-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. +If an audience is specified in the request, any scopes that belong to that audience and are granted to the user via their role will also be granted to the device. These scopes are shown on the consent screen and, upon consent, included in the access token’s `scope` claim.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
(1 hunks)src/content/docs/authenticate/device-authorization-flow/overview.mdx
(1 hunks)src/content/docs/authenticate/device-authorization-flow/quick-start.mdx
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/content/docs/authenticate/device-authorization-flow/quick-start.mdx
🧰 Additional context used
🪛 Gitleaks (8.27.2)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
20-21: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
31-32: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
222-224: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
🔇 Additional comments (4)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx (1)
71-73
: No changes required: JWKS endpoint is correct and OIDC discovery is supportedThe JWKS URI for Kinde tenants is indeed
https://.kinde.com/.well-known/jwks
(without a.json
suffix), and Kinde publishes a standard OpenID Connect Discovery document at
https://.kinde.com/.well-known/openid-configuration
which includes thejwks_uri
field pointing to the same endpoint.• File: src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
• Lines: 71–73src/content/docs/authenticate/device-authorization-flow/overview.mdx (3)
91-94
: Verify polling “Maximum time” versus earlier expires_in exampleYou list expires_in: 600 (10 minutes) in the authorization response but “typically 30 minutes” here. Align with Kinde’s actual values.
Would you like me to search Kinde docs and update both places consistently?
105-110
: Verify default access token lifetime“Access tokens expire after 1 hour by default” may vary per tenant/policy.
Confirm Kinde’s default and whether it’s configurable, then reflect that nuance.
111-116
: Verify multiple audiences support and app/API authorization noteMultiple audiences support is provider-specific. Confirm Kinde behavior and prerequisites (authorizing API for the device app).
If supported, consider linking to the specific Kinde docs page.
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx (2)
66-93
: Strengthen JWT verification: correct JWKS URL, enable caching/rate limiting, validate issuer/audience, and handle JWKS errorsThe sample verifies signatures but misses critical hardening:
- Use the OIDC-standard JWKS path (jwks.json).
- Enable jwks-rsa caching/rate limiting.
- Validate issuer and audience.
- Handle getSigningKey errors and prefer getPublicKey() when available.
These are security-sensitive defaults.
-// Node.js example using jsonwebtoken with JWKS -const jwt = require("jsonwebtoken"); -const jwksClient = require("jwks-rsa"); - -const client = jwksClient({ - jwksUri: "https://<your-subdomain>.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 }); - } - }); - }); -} +// Node.js example using jsonwebtoken with JWKS +const jwt = require("jsonwebtoken"); +const jwksClient = require("jwks-rsa"); + +const client = jwksClient({ + jwksUri: "https://<your-subdomain>.kinde.com/.well-known/jwks.json", + cache: true, + cacheMaxEntries: 5, + cacheMaxAge: 10 * 60 * 1000, // 10 minutes + rateLimit: true, + jwksRequestsPerMinute: 10 +}); + +function getKey(header, callback) { + client.getSigningKey(header.kid, (err, key) => { + if (err) return callback(err); + const signingKey = key.getPublicKey ? key.getPublicKey() : (key.publicKey || key.rsaPublicKey); + callback(null, signingKey); + }); +} + +function validateToken(token) { + const verifyOptions = { + algorithms: ["RS256"], + issuer: "https://<your-subdomain>.kinde.com", + audience: "<your-api-audience>" + }; + return new Promise((resolve) => { + jwt.verify(token, getKey, verifyOptions, (err, decoded) => { + if (err) { + return resolve({ valid: false, error: err.message }); + } + resolve({ valid: true, user: decoded }); + }); + }); +}
99-111
: Do not use jwt.decode for scope checks — verify the token firstjwt.decode does not verify signatures; forged tokens could pass scope checks. Verify first, then inspect claims. Also, operate on decoded claims rather than raw token strings for clarity.
-// 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); -} +// Example: Check if decoded claims include required scope +function hasRequiredScopeFromClaims(claims, requiredScope) { + const raw = claims && claims.scope ? String(claims.scope) : ""; + const tokenScopes = raw.split(" ").filter(Boolean); + return tokenScopes.includes(requiredScope); +} -// Usage -if (!hasRequiredScope(accessToken, "read:users")) { - return res.status(403).json({error: "Insufficient scope"}); -} +// Usage (verify first, then check scopes) +try { + const decoded = jwt.verify(accessToken, getKey, { + algorithms: ["RS256"], + issuer: "https://<your-subdomain>.kinde.com", + audience: "<your-api-audience>" + }); + if (!hasRequiredScopeFromClaims(decoded, "read:users")) { + return res.status(403).json({ error: "Insufficient scope" }); + } +} catch (e) { + return res.status(401).json({ error: "Invalid token" }); +}
🧹 Nitpick comments (3)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx (3)
3-3
: Align title capitalization with docs namingUse "Device Authorization Flow" for consistency with sidebar/nav and other pages.
-title: Call your API using device authorization flow +title: Call your API using Device Authorization Flow
206-214
: Avoid logging PII; use standard claims and guard missing scopesUse sub instead of a non-standard user_id, and default scope to empty. Consider redacting or hashing identifiers in real apps.
function logAuthEvent(token, action, success) { console.log({ timestamp: new Date().toISOString(), action: action, success: success, - userId: token.user_id, - scopes: token.scope + userId: token.sub, // standard subject claim + scopes: token.scope || "" // guard missing claim }); }
19-22
: Prevent accidental token leakage in docs snippetsThese examples use placeholders, but scanners can flag them. Add a short note to discourage pasting real tokens into docs and CI logs.
curl -X GET https://your-api.com/protected-resource \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" +// Note: Replace YOUR_ACCESS_TOKEN when testing locally. Do not commit real tokens.
curl -X GET https://<your-subdomain>.kinde.com/oauth2/v2/user_profile \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" +// Note: Replace YOUR_ACCESS_TOKEN when testing locally. Do not commit real tokens.
curl -X GET https://your-api.com/protected-resource \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" +# Note: Replace YOUR_ACCESS_TOKEN when testing locally. Do not commit real tokens.
Also applies to: 31-33, 222-224
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
(1 hunks)
🧰 Additional context used
🪛 Gitleaks (8.27.2)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx
20-21: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
31-32: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
222-224: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
🔇 Additional comments (1)
src/content/docs/authenticate/device-authorization-flow/api-calls.mdx (1)
192-199
: Rate limiting example looks goodClear, sane defaults for window and max requests. Route-scoped application is appropriate.
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://<your-subdomain>.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"}); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Prefer local JWT validation in middleware; avoid per-request network hop to user_profile
Round-tripping to Kinde for every request adds latency and a failure mode. Since a valid JWT already proves authenticity, validate locally and attach claims to req.user. If you keep the fetch approach, add timeout/error handling and note that user_profile does not carry scopes.
-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://<your-subdomain>.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"});
- });
-}
+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" });
+
+ validateToken(token).then((result) => {
+ if (!result.valid) return res.status(401).json({ error: result.error || "Invalid token" });
+ req.user = result.user; // decoded claims
+ next();
+ }).catch(() => res.status(401).json({ error: "Invalid token" }));
+}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
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://<your-subdomain>.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"}); | |
}); | |
} | |
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" }); | |
validateToken(token) | |
.then((result) => { | |
if (!result.valid) return res.status(401).json({ error: result.error || "Invalid token" }); | |
req.user = result.user; // decoded claims | |
next(); | |
}) | |
.catch(() => res.status(401).json({ error: "Invalid token" })); | |
} |
🤖 Prompt for AI Agents
In src/content/docs/authenticate/device-authorization-flow/api-calls.mdx around
lines 127 to 154, the middleware currently fetches /user_profile on every
request; instead implement local JWT validation: extract the Bearer token,
download/refresh the provider JWKS (cache keys and respect cache-control),
verify token signature, issuer, audience, and expiry, then set req.user to the
decoded claims and call next(); if validation fails return 401. If you choose to
keep the remote fetch approach, add a request timeout, proper error handling for
non-200 responses and network errors, and a comment that user_profile does not
include scopes so you must rely on token claims for authorization.
```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"}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
handleTokenError expects error.error, but jwt.verify throws typed errors
JsonWebTokenError, TokenExpiredError, etc., won’t have an error field. Map by error.name and still support structured errors from HTTP responses.
-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"});
- }
-}
+function handleTokenError(res, error) {
+ const code = error?.error || error?.name;
+ switch (code) {
+ case "invalid_token":
+ case "JsonWebTokenError":
+ case "NotBeforeError":
+ return res.status(401).json({ error: "Please re-authenticate" });
+ case "TokenExpiredError":
+ return res.status(401).json({ error: "Session expired. Please re-authenticate" });
+ case "insufficient_scope":
+ return res.status(403).json({ error: "Insufficient permissions" });
+ default:
+ return res.status(500).json({ error: "Authentication error" });
+ }
+}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```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"}); | |
} | |
} | |
function handleTokenError(res, error) { | |
const code = error?.error || error?.name; | |
switch (code) { | |
case "invalid_token": | |
case "JsonWebTokenError": | |
case "NotBeforeError": | |
return res.status(401).json({ error: "Please re-authenticate" }); | |
case "TokenExpiredError": | |
return res.status(401).json({ error: "Session expired. Please re-authenticate" }); | |
case "insufficient_scope": | |
return res.status(403).json({ error: "Insufficient permissions" }); | |
default: | |
return res.status(500).json({ error: "Authentication error" }); | |
} | |
} |
🤖 Prompt for AI Agents
In src/content/docs/authenticate/device-authorization-flow/api-calls.mdx around
lines 161 to 175, the handler currently inspects error.error but JWT runtime
errors use error.name (e.g., JsonWebTokenError, TokenExpiredError) and HTTP
responses may provide a structured error field; update the function to first
branch on error.name for known JWT errors (map TokenExpiredError -> 401 with
"Please re-authenticate", JsonWebTokenError -> 401 with generic auth error,
etc.), fall back to checking error.error for structured HTTP error payloads
(mapping "insufficient_scope" -> 403), and finally return a 500 for unknown
errors; ensure the response messages and status codes match the original intent
and preserve any original error details when appropriate.
Documentation for the new Device Authorization Flow feature
Summary by CodeRabbit
New Features
Documentation
Style