Skip to content

Commit 4cd6439

Browse files
Merge pull request #515 from kinde-oss/feat/device-flow-docs
Device authorization flow new topics. Approved by Dave and Ev
2 parents d94f906 + 4877d6b commit 4cd6439

File tree

6 files changed

+709
-38
lines changed

6 files changed

+709
-38
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
---
2+
page_id: 8d8234a5-b064-47a1-a1e1-9b3f98a57f34
3+
title: Call your API using device authorization flow
4+
sidebar:
5+
order: 3
6+
relatedArticles:
7+
- 2944d2bc-4e84-4918-b4f6-7406a7c26f98
8+
- 888b1546-8047-4609-af59-8cf859527aa0
9+
- de937e16-8094-4aad-ada9-e6a37d74f508
10+
- 1cbd91d2-c0b3-45b3-b038-319de1b2c794
11+
---
12+
13+
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.
14+
15+
## Use the access token from the device authorization flow
16+
17+
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:
18+
19+
```bash
20+
curl -X GET https://your-api.com/protected-resource \
21+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
22+
```
23+
24+
## Token validation in the device authorization flow
25+
26+
Before processing API requests, validate the access token to ensure it's valid and hasn't expired:
27+
28+
### Validate with Kinde's userinfo endpoint
29+
30+
```bash
31+
curl -X GET https://<your-subdomain>.kinde.com/oauth2/v2/user_profile \
32+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
33+
```
34+
35+
**Success response**:
36+
37+
```json
38+
{
39+
"sub": "kp_c3143a4b50ad43c88e541d9077681782",
40+
"provided_id": "some_external_id",
41+
"name": "John Snow",
42+
"given_name": "John",
43+
"family_name": "Snow",
44+
"updated_at": 1612345678,
45+
"email": "[email protected]",
46+
"email_verified": true,
47+
"picture": "https://example.com/john_snow.jpg",
48+
"preferred_username": "john_snow",
49+
"id": "kp_c3143a4b50ad43c88e541d9077681782"
50+
}
51+
```
52+
53+
**Error response** (invalid token):
54+
55+
```json
56+
{
57+
"error": "invalid_token",
58+
"error_description": "The access token is invalid or expired"
59+
}
60+
```
61+
62+
### Validate with your own API
63+
64+
You can also validate tokens in your own API by verifying the JWT signature and claims:
65+
66+
```javascript
67+
+// Node.js example using jsonwebtoken with JWKS
68+
+const jwt = require("jsonwebtoken");
69+
+const jwksClient = require("jwks-rsa");
70+
+
71+
+const client = jwksClient({
72+
+ jwksUri: "https://<your-subdomain>.kinde.com/.well-known/jwks"
73+
+});
74+
+
75+
+function getKey(header, callback) {
76+
+ client.getSigningKey(header.kid, (err, key) => {
77+
+ const signingKey = key.publicKey || key.rsaPublicKey;
78+
+ callback(null, signingKey);
79+
+ });
80+
+}
81+
+
82+
+function validateToken(token) {
83+
+ return new Promise((resolve, reject) => {
84+
+ jwt.verify(token, getKey, { algorithms: ["RS256"] }, (err, decoded) => {
85+
+ if (err) {
86+
+ resolve({ valid: false, error: err.message });
87+
+ } else {
88+
+ resolve({ valid: true, user: decoded });
89+
+ }
90+
+ });
91+
+ });
92+
+}
93+
```
94+
95+
## Scope enforcement for device authorization
96+
97+
Access tokens include scopes that determine what resources the user can access. Check the required scopes before processing requests:
98+
99+
```javascript
100+
// Example: Check if user has required scope
101+
function hasRequiredScope(token, requiredScope) {
102+
const decoded = jwt.decode(token);
103+
const tokenScopes = decoded.scope.split(" ");
104+
return tokenScopes.includes(requiredScope);
105+
}
106+
107+
// Usage
108+
if (!hasRequiredScope(accessToken, "read:users")) {
109+
return res.status(403).json({error: "Insufficient scope"});
110+
}
111+
```
112+
113+
## Common API patterns for device authorization
114+
115+
### Protected resource endpoint
116+
117+
```javascript
118+
// Express.js example
119+
app.get("/api/protected-resource", authenticateToken, (req, res) => {
120+
// req.user contains the decoded token payload
121+
res.json({
122+
message: "Access granted",
123+
user: req.user
124+
});
125+
});
126+
127+
function authenticateToken(req, res, next) {
128+
const authHeader = req.headers["authorization"];
129+
const token = authHeader && authHeader.split(" ")[1];
130+
131+
if (!token) {
132+
return res.status(401).json({error: "Access token required"});
133+
}
134+
135+
// Validate token with Kinde
136+
fetch("https://<your-subdomain>.kinde.com/oauth2/v2/user_profile", {
137+
headers: {
138+
Authorization: `Bearer ${token}`
139+
}
140+
})
141+
.then((response) => {
142+
if (!response.ok) {
143+
throw new Error("Invalid token");
144+
}
145+
return response.json();
146+
})
147+
.then((user) => {
148+
req.user = user;
149+
next();
150+
})
151+
.catch((error) => {
152+
return res.status(401).json({error: "Invalid token"});
153+
});
154+
}
155+
```
156+
157+
### Error handling for device authorization
158+
159+
Handle common token-related errors:
160+
161+
```javascript
162+
function handleTokenError(res, error) {
163+
switch (error.error) {
164+
case "invalid_token":
165+
// Token is invalid or expired
166+
return res.status(401).json({error: "Please re-authenticate"});
167+
168+
case "insufficient_scope":
169+
// Token doesn't have required permissions
170+
return res.status(403).json({error: "Insufficient permissions"});
171+
172+
default:
173+
return res.status(500).json({error: "Authentication error"});
174+
}
175+
}
176+
```
177+
178+
## Security best practices for device authorization
179+
180+
### Token storage
181+
182+
- **Never store tokens in localStorage**: Use secure HTTP-only cookies or memory storage
183+
- **Validate tokens server-side**: Always validate tokens on your backend, not just the client
184+
185+
### Rate limiting
186+
187+
Implement rate limiting for token validation requests:
188+
189+
```javascript
190+
const rateLimit = require("express-rate-limit");
191+
192+
const tokenValidationLimiter = rateLimit({
193+
windowMs: 15 * 60 * 1000, // 15 minutes
194+
max: 100, // limit each IP to 100 requests per windowMs
195+
message: "Too many token validation requests"
196+
});
197+
198+
app.use("/api/protected-resource", tokenValidationLimiter);
199+
```
200+
201+
### Logging and monitoring
202+
203+
Log authentication events for security monitoring:
204+
205+
```javascript
206+
function logAuthEvent(token, action, success) {
207+
console.log({
208+
timestamp: new Date().toISOString(),
209+
action: action,
210+
success: success,
211+
userId: token.user_id,
212+
scopes: token.scope
213+
});
214+
}
215+
```
216+
217+
## Testing your API
218+
219+
Test your protected endpoints with the access token:
220+
221+
```bash
222+
# Test with curl
223+
curl -X GET https://your-api.com/protected-resource \
224+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
225+
226+
# Test with JavaScript
227+
fetch('https://your-api.com/protected-resource', {
228+
headers: {
229+
'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
230+
}
231+
})
232+
.then(response => response.json())
233+
.then(data => console.log(data));
234+
```
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
page_id: ab3834cc-4645-4b7a-826a-c7f502eee3dd
3+
title: About the device authorization flow
4+
sidebar:
5+
order: 2
6+
relatedArticles:
7+
- 2944d2bc-4e84-4918-b4f6-7406a7c26f98
8+
- 8d8234a5-b064-47a1-a1e1-9b3f98a57f34
9+
- 888b1546-8047-4609-af59-8cf859527aa0
10+
- 28c6e830-8e82-4bf8-aab7-87ebafeb68e4
11+
---
12+
13+
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.
14+
15+
## How the device authentication flow works
16+
17+
1. **Device requests authorization**: The device requests a device code and user code from Kinde.
18+
2. **User authenticates**: The user visits a verification URI on another device and enters the user code.
19+
3. **Device polls for token**: The device polls the token endpoint until authorization is complete.
20+
4. **Access granted**: The device receives an access token and can call protected APIs.
21+
22+
## Endpoints for the device authorization flow
23+
24+
### Device authorization endpoint
25+
26+
**URL**: `https://<your-subdomain>.kinde.com/oauth2/device/auth`
27+
28+
**Method**: `POST`
29+
30+
**Content-Type**: `application/x-www-form-urlencoded`
31+
32+
**Parameters**:
33+
34+
- `client_id` (optional): Your application's client ID - can be omitted if you have set an application as the default for device flows
35+
- `audience` (optional): The audience to use for the request
36+
37+
**Response**:
38+
39+
```json
40+
{
41+
"device_code": "kinde_dc_device_code_here",
42+
"user_code": "CSLDFDUU",
43+
"verification_uri": "https://<your-subdomain>.kinde.com/device",
44+
"verification_uri_complete": "https://<your-subdomain>.kinde.com/device?user_code=CSLDFDUU",
45+
"expires_in": 600,
46+
"interval": 5,
47+
"qr_code": "data:image/png;base64,..."
48+
}
49+
```
50+
51+
### Token endpoint
52+
53+
**URL**: `https://<your-subdomain>.kinde.com/oauth2/token`
54+
55+
**Method**: `POST`
56+
57+
**Content-Type**: `application/x-www-form-urlencoded`
58+
59+
**Parameters**:
60+
61+
- `grant_type`: `urn:ietf:params:oauth:grant-type:device_code`
62+
- `client_id`: Your application's client ID
63+
- `device_code`: The device code received from the authorization endpoint
64+
65+
**Success response**:
66+
67+
```json
68+
{
69+
"access_token": "eyJ...",
70+
"expires_in": 86400,
71+
"scope": "",
72+
"token_type": "bearer"
73+
}
74+
```
75+
76+
The scope field may be empty because granted scopes are carried in the access token’s scope claim.
77+
78+
**Example error response**:
79+
80+
```json
81+
{
82+
"error": "authorization_pending",
83+
"error_description": "The user has not yet completed the authorization"
84+
}
85+
```
86+
87+
## Polling behavior
88+
89+
The device must poll the token endpoint at regular intervals until the user completes authentication:
90+
91+
- **Initial interval**: Use the `interval` value from the device authorization response (typically 5 seconds).
92+
- **Slow down**: If you receive a `slow_down` error, increase the polling interval by 5 seconds.
93+
- **Maximum time**: Stop polling after the `expires_in` time (typically 30 minutes).
94+
95+
## Device authorization flow error codes
96+
97+
| Error Code | Description | Action |
98+
| ----------------------- | ------------------------------------ | ------------------------------ |
99+
| `authorization_pending` | User hasn't completed authentication | Continue polling |
100+
| `slow_down` | Polling too frequently | Increase interval by 5 seconds |
101+
| `access_denied` | User denied the authorization | Stop polling |
102+
| `expired_token` | Device code has expired | Request a new device code |
103+
| `server_error` | Misconfigured device code | Request a new device code |
104+
105+
## Security considerations for device authorization
106+
107+
- **User code format**: User codes are formatted as `XXXXXXXX` for easy entry.
108+
- **Verification URI**: Users should verify they're on the correct domain.
109+
- **Token expiration**: Access tokens expire after 1 hour by default.
110+
111+
## Specifying an audience in a device authorization request
112+
113+
If an `audience` is specified in the request, the access token will include the audience in the `aud` claim. Kinde supports requesting multiple audiences.
114+
115+
The API must be authorized for the device authorization application.
116+
117+
## Scopes and permissions for a device authorization request
118+
119+
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.

0 commit comments

Comments
 (0)