-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathOauthStructure
More file actions
379 lines (286 loc) · 6.96 KB
/
Copy pathOauthStructure
File metadata and controls
379 lines (286 loc) · 6.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
<div>
If you mean the structure/flow of OAuth 2.0 + PKCE, the typical structure is:
User / Client App
│
│ 1. Generate:
│ code_verifier
│ code_challenge = BASE64URL(SHA256(code_verifier))
│
│ 2. Authorization Request
▼
Authorization Server
│
│ 3. User authenticates + consents
│
│ 4. Redirect with authorization_code
▼
Client App
│
│ 5. Token Request:
│ code
│ code_verifier
▼
Authorization Server
│
│ 6. Verify code_verifier
│
│ 7. Return access_token
│ (+ refresh_token, optionally)
▼
Client App
│
│ 8. API request:
│ Authorization: Bearer <access_token>
▼
Resource Server / API
Core parameters
Authorization request:
GET /authorize
?response_type=code
&client_id=CLIENT_ID
&redirect_uri=REDIRECT_URI
&scope=openid profile
&state=STATE
&code_challenge=CODE_CHALLENGE
&code_challenge_method=S256
Token request:
POST /token
grant_type=authorization_code
code=AUTHORIZATION_CODE
redirect_uri=REDIRECT_URI
client_id=CLIENT_ID
code_verifier=CODE_VERIFIER
PKCE relationship
The important part is:
code_verifier
│
└── SHA-256
│
└── Base64URL
│
▼
code_challenge
The authorization server stores/associates the code_challenge with the authorization code. At the token exchange, it calculates the challenge from the supplied code_verifier and compares them.
Why: if an attacker steals the authorization code, they still cannot exchange it for tokens without the original code_verifier.
For modern applications, use S256, not plain.
Sure — here’s a minimal OAuth 2.0 Authorization Code + PKCE implementation structure in JavaScript/Node.js.
1. Generate PKCE
import crypto from "crypto";
function base64url(buffer) {
return buffer
.toString("base64")
.replace(/+/g, "-")
.replace(///g, "_")
.replace(/=/g, "");
}
function generatePKCE() {
const codeVerifier = base64url(crypto.randomBytes(32));
const codeChallenge = base64url(
crypto
.createHash("sha256")
.update(codeVerifier)
.digest()
);
return {
codeVerifier,
codeChallenge,
};
}
2. Authorization URL
const { codeVerifier, codeChallenge } = generatePKCE();
// Store codeVerifier temporarily in the user's session
req.session.codeVerifier = codeVerifier;
const params = new URLSearchParams({
response_type: "code",
client_id: process.env.CLIENT_ID,
redirect_uri: process.env.REDIRECT_URI,
scope: "openid profile email",
state: crypto.randomBytes(16).toString("hex"),
code_challenge: codeChallenge,
code_challenge_method: "S256",
});
res.redirect(
${process.env.AUTHORIZATION_ENDPOINT}?${params.toString()}
);
3. Callback
After login, the authorization server redirects to:
/callback?code=AUTHORIZATION_CODE&state=...
Then exchange the code:
app.get("/callback", async (req, res) => {
const { code } = req.query;
const codeVerifier = req.session.codeVerifier;
const response = await fetch(
process.env.TOKEN_ENDPOINT,
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: process.env.CLIENT_ID,
code,
redirect_uri: process.env.REDIRECT_URI,
code_verifier: codeVerifier,
}),
}
);
const tokens = await response.json();
// tokens.access_token
// tokens.refresh_token
// tokens.id_token
res.json(tokens);
});
Overall project structure
oauth-pkce/
├── src/
│ ├── auth/
│ │ ├── pkce.js
│ │ ├── authorize.js
│ │ └── callback.js
│ │
│ ├── api/
│ │ └── protected.js
│ │
│ └── server.js
│
├── .env
└── package.json
If you tell me your stack (React, Next.js, Node/Express, Spring Boot, .NET, Flutter, Android, etc.), I can give you the complete OAuth2 + PKCE code for that stack.
Yes. For FastAPI, if you want to implement OAuth 2.0 Authorization Code + PKCE, a clean structure is:
app/
├── main.py
├── auth/
│ ├── routes.py
│ ├── pkce.py
│ └── oauth.py
├── config.py
└── requirements.txt
pkce.py
import base64
import hashlib
import secrets
def base64url(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b"=").decode()
def generate_pkce():
code_verifier = base64url(secrets.token_bytes(32))
code_challenge = base64url(
hashlib.sha256(
code_verifier.encode()
).digest()
)
return code_verifier, code_challenge
routes.py
from fastapi import APIRouter, Request
from fastapi.responses import RedirectResponse
import httpx
import secrets
from .pkce import generate_pkce
router = APIRouter()
AUTHORIZATION_ENDPOINT = "https://auth.example.com/authorize"
TOKEN_ENDPOINT = "https://auth.example.com/token"
CLIENT_ID = "your-client-id"
REDIRECT_URI = "http://localhost:8000/auth/callback"
@router.get("/login")
async def login(request: Request):
code_verifier, code_challenge = generate_pkce()
# Store verifier in the user's session
request.session["code_verifier"] = code_verifier
# CSRF protection
state = secrets.token_urlsafe(32)
request.session["oauth_state"] = state
params = {
"response_type": "code",
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"scope": "openid profile email",
"state": state,
"code_challenge": code_challenge,
"code_challenge_method": "S256",
}
url = httpx.URL(
AUTHORIZATION_ENDPOINT,
params=params,
)
return RedirectResponse(str(url))
Callback
@router.get("/callback")
async def callback(
request: Request,
code: str,
state: str,
):
# Verify state
if state != request.session.get("oauth_state"):
return {"error": "Invalid state"}
code_verifier = request.session.get("code_verifier")
if not code_verifier:
return {"error": "Missing PKCE verifier"}
data = {
"grant_type": "authorization_code",
"client_id": CLIENT_ID,
"code": code,
"redirect_uri": REDIRECT_URI,
"code_verifier": code_verifier,
}
async with httpx.AsyncClient() as client:
response = await client.post(
TOKEN_ENDPOINT,
data=data,
)
if response.status_code != 200:
return {
"error": "Token exchange failed",
"details": response.text,
}
tokens = response.json()
# Clean up
request.session.pop("code_verifier", None)
request.session.pop("oauth_state", None)
return tokens
main.py
You'll need session middleware because the code_verifier must survive the redirect:
from fastapi import FastAPI
from starlette.middleware.sessions import SessionMiddleware
from app.auth.routes import router
app = FastAPI()
app.add_middleware(
SessionMiddleware,
secret_key="CHANGE_THIS_TO_A_RANDOM_SECRET",
)
app.include_router(
router,
prefix="/auth",
)
Install:
pip install fastapi uvicorn httpx itsdangerous
Run:
uvicorn app.main:app --reload
Then:
http://localhost:8000/auth/login
│
▼
Authorization Server
│
│ authorization_code
▼
/auth/callback
│
│ code + code_verifier
▼
Token Endpoint
│
▼
access_token
Important: for production, don't hard-code CLIENT_ID, endpoints, or the session secret; put them in environment variables. Also validate state and use S256 PKCE as shown above.
docs/
README.md
README.md
API documentation
Installation and setup guide
Architecture overview
Deployment guide
Troubleshooting guide
Contributing guidelines
Release notes or changelog
</div>