|
| 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 | + |
| 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 | +``` |
0 commit comments