Skip to main content

max / makenotwork

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