Skip to main content

max / makenotwork

4.3 KB · 165 lines History Blame Raw
1 # OAuth2 PKCE
2
3 Makenot.work supports OAuth2 Authorization Code with PKCE for "Log in with Makenot.work" flows. This lets third-party applications authenticate users without handling their passwords directly.
4
5 ## Overview
6
7 1. Your app generates a PKCE code verifier and challenge
8 2. User is redirected to `makenot.work/oauth/authorize` to log in and consent
9 3. Makenot.work redirects back with an authorization code
10 4. Your app exchanges the code for a JWT access token
11 5. Use the token to call SyncKit or userinfo endpoints
12
13 ## Client Registration
14
15 Your OAuth client ID is the API key of your SyncKit app. Create a SyncKit app from the Makenot.work dashboard to get one.
16
17 ### Redirect URIs
18
19 **Localhost**: `http://127.0.0.1:{port}/...` and `http://localhost:{port}/...` are always allowed without registration. Use these for desktop apps.
20
21 **Remote**: Non-localhost redirect URIs must be registered on your SyncKit app. Email info@makenot.work to add them.
22
23 ## Authorization Request
24
25 Redirect the user to the authorize endpoint:
26
27 ```
28 GET /oauth/authorize
29 ?response_type=code
30 &client_id=<your-api-key>
31 &redirect_uri=http://127.0.0.1:8765/callback
32 &state=<random-string>
33 &code_challenge=<S256-challenge>
34 &code_challenge_method=S256
35 ```
36
37 | Parameter | Required | Description |
38 |-----------|----------|-------------|
39 | `response_type` | Yes | Must be `code` |
40 | `client_id` | Yes | Your SyncKit app API key |
41 | `redirect_uri` | Yes | Where to send the authorization code |
42 | `state` | Yes | Random string to prevent CSRF; verify it in the callback |
43 | `code_challenge` | Yes | Base64url-encoded SHA-256 hash of the code verifier |
44 | `code_challenge_method` | Yes | Must be `S256` |
45
46 The user sees a consent page. After logging in and approving, they are redirected to:
47
48 ```
49 {redirect_uri}?code=<authorization-code>&state=<your-state>
50 ```
51
52 ## Token Exchange
53
54 Exchange the authorization code for an access token:
55
56 ```
57 POST /oauth/token
58 Content-Type: application/json
59
60 {
61 "grant_type": "authorization_code",
62 "code": "<authorization-code>",
63 "redirect_uri": "http://127.0.0.1:8765/callback",
64 "code_verifier": "<original-code-verifier>",
65 "client_id": "<your-api-key>"
66 }
67 ```
68
69 Response:
70
71 ```json
72 {
73 "access_token": "eyJ...",
74 "token_type": "Bearer",
75 "expires_in": 604800,
76 "user_id": "550e8400-...",
77 "app_id": "660f9500-..."
78 }
79 ```
80
81 The authorization code is single-use. The server verifies `SHA256(code_verifier) == code_challenge` before issuing a token.
82
83 ## User Info
84
85 Retrieve the authenticated user's profile:
86
87 ```
88 GET /oauth/userinfo
89 Authorization: Bearer <access_token>
90 ```
91
92 Response:
93
94 ```json
95 {
96 "user_id": "550e8400-...",
97 "username": "alice",
98 "display_name": "Alice",
99 "avatar_url": "https://makenot.work/static/avatars/alice.jpg"
100 }
101 ```
102
103 ## PKCE Implementation
104
105 PKCE prevents authorization code interception:
106
107 1. Generate a random code verifier (43-128 characters, URL-safe)
108 2. Compute `code_challenge = BASE64URL(SHA256(code_verifier))`
109 3. Send `code_challenge` in the authorization request
110 4. Send `code_verifier` in the token exchange
111
112 The server rejects token requests where the verifier does not match the challenge.
113
114 ### Example (Rust)
115
116 ```rust
117 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
118 use sha2::{Digest, Sha256};
119
120 let verifier: String = (0..64)
121 .map(|_| rand::random::<u8>())
122 .map(|b| format!("{:02x}", b))
123 .collect();
124
125 let challenge = URL_SAFE_NO_PAD.encode(Sha256::digest(verifier.as_bytes()));
126 ```
127
128 ## Token Usage
129
130 The access token works with all SyncKit endpoints:
131
132 - [Cloud Sync]./synckit.md: push/pull data, manage devices
133 - [OTA Updates]./ota.md: manage releases and artifacts
134 - User info (above)
135
136 Tokens expire after 7 days. After expiration, redirect the user through the authorization flow again.
137
138 ## Error Handling
139
140 Authorization errors redirect to `redirect_uri` with an `error` parameter:
141
142 ```
143 {redirect_uri}?error=access_denied&state=<your-state>
144 ```
145
146 Token exchange errors return JSON:
147
148 ```json
149 {
150 "error": "invalid_grant"
151 }
152 ```
153
154 | Error | Meaning |
155 |-------|---------|
156 | `access_denied` | User denied consent |
157 | `invalid_client` | Unknown client_id |
158 | `invalid_grant` | Code expired, already used, or verifier mismatch |
159 | `invalid_request` | Missing required parameters |
160
161 ## See Also
162
163 - [API Overview]./api-overview.md: authentication methods and rate limits
164 - [SyncKit Cloud Sync]./synckit.md: using the token for data sync
165