Skip to content

Commit 269f7a9

Browse files
committed
feat: initial commit of device flow
1 parent 3152b8b commit 269f7a9

File tree

6 files changed

+684
-10
lines changed

6 files changed

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