0
# OpenID Connect (OIDC) Support
1
2
Django OAuth Toolkit provides comprehensive OpenID Connect 1.0 implementation, adding an identity layer on top of OAuth2. This includes discovery endpoints, UserInfo, JSON Web Key Sets (JWKS), ID tokens, and relying party initiated logout.
3
4
## Capabilities
5
6
### OIDC Discovery Endpoint
7
8
OpenID Connect Provider Configuration Information endpoint implementing the discovery specification.
9
10
```python { .api }
11
class ConnectDiscoveryInfoView(View):
12
"""
13
OIDC discovery endpoint (/.well-known/openid-configuration).
14
15
Provides OpenID Provider metadata for client configuration.
16
Returns JSON with provider capabilities and endpoint URLs.
17
18
Methods:
19
GET: Return OIDC discovery document
20
21
Returns:
22
JSON document with:
23
- issuer: Provider issuer identifier
24
- authorization_endpoint: OAuth2 authorization URL
25
- token_endpoint: OAuth2 token URL
26
- userinfo_endpoint: OIDC UserInfo URL
27
- jwks_uri: JSON Web Key Set URL
28
- end_session_endpoint: Logout URL
29
- scopes_supported: List of supported OAuth2 scopes
30
- response_types_supported: Supported OAuth2 response types
31
- grant_types_supported: Supported OAuth2 grant types
32
- subject_types_supported: Subject identifier types
33
- id_token_signing_alg_values_supported: ID token algorithms
34
- token_endpoint_auth_methods_supported: Client auth methods
35
"""
36
37
def get(self, request, *args, **kwargs):
38
"""Return OIDC discovery document"""
39
```
40
41
### UserInfo Endpoint
42
43
OIDC UserInfo endpoint for retrieving user profile information using access tokens.
44
45
```python { .api }
46
class UserInfoView(View):
47
"""
48
OIDC UserInfo endpoint (/o/userinfo/).
49
50
Returns user profile information for the access token owner.
51
Requires valid access token with 'openid' scope.
52
53
Methods:
54
GET: Return user information
55
POST: Return user information (alternative method)
56
57
Headers:
58
Authorization: Bearer ACCESS_TOKEN
59
60
Returns:
61
JSON with user claims:
62
- sub: Subject identifier (user ID)
63
- name: Full name
64
- given_name: First name
65
- family_name: Last name
66
- email: Email address
67
- email_verified: Email verification status
68
- picture: Profile picture URL
69
- Additional claims based on scopes and configuration
70
"""
71
72
def get(self, request, *args, **kwargs):
73
"""Return UserInfo for the token owner"""
74
75
def post(self, request, *args, **kwargs):
76
"""Alternative POST method for UserInfo"""
77
```
78
79
### JSON Web Key Set (JWKS) Endpoint
80
81
OIDC JWKS endpoint for public key distribution for ID token verification.
82
83
```python { .api }
84
class JwksInfoView(View):
85
"""
86
OIDC JWKS endpoint (/.well-known/jwks.json).
87
88
Provides public keys for verifying ID tokens and other JWTs.
89
Returns JSON Web Key Set containing cryptographic keys.
90
91
Methods:
92
GET: Return JWKS document
93
94
Returns:
95
JSON Web Key Set with:
96
- keys: Array of JWK objects containing public keys
97
- Each key includes: kty, use, kid, n, e (for RSA keys)
98
- Supports RSA and HMAC signing algorithms
99
"""
100
101
def get(self, request, *args, **kwargs):
102
"""Return JSON Web Key Set"""
103
```
104
105
### Relying Party Initiated Logout
106
107
OIDC logout endpoint allowing relying parties to initiate user logout.
108
109
```python { .api }
110
class RPInitiatedLogoutView(View):
111
"""
112
OIDC Relying Party Initiated Logout endpoint (/o/logout/).
113
114
Handles logout requests from OIDC clients (relying parties).
115
Supports both GET and POST methods with logout confirmation.
116
117
Methods:
118
GET: Display logout confirmation form
119
POST: Process logout confirmation
120
121
Query/Form Parameters:
122
id_token_hint: ID token to identify the user session
123
logout_hint: Hint about user identity for logout
124
client_id: OIDC client identifier
125
post_logout_redirect_uri: Where to redirect after logout
126
state: Client state parameter
127
ui_locales: Preferred UI locales
128
129
Returns:
130
Logout confirmation form or redirect to post_logout_redirect_uri
131
"""
132
133
template_name = "oauth2_provider/rp_initiated_logout.html"
134
form_class = ConfirmLogoutForm
135
136
def get(self, request, *args, **kwargs):
137
"""Display logout confirmation"""
138
139
def post(self, request, *args, **kwargs):
140
"""Process logout confirmation"""
141
```
142
143
### ID Token Model
144
145
OIDC ID token model for storing JWT token metadata and claims.
146
147
```python { .api }
148
class IDToken(AbstractIDToken):
149
"""
150
OIDC ID token model (already covered in models.md but relevant here).
151
152
Stores metadata about issued ID tokens for OIDC flows.
153
Links to access tokens and provides JWT token identification.
154
"""
155
156
jti: uuid.UUID # JWT Token ID
157
user: User # Subject user
158
application: Application # OIDC client
159
expires: datetime # Token expiration
160
scope: str # Token scopes
161
```
162
163
### OIDC URL Patterns
164
165
URL patterns for OpenID Connect endpoints.
166
167
```python { .api }
168
oidc_urlpatterns = [
169
# OIDC discovery endpoint (supports both with and without trailing slash)
170
re_path(
171
r"^\.well-known/openid-configuration/?$",
172
views.ConnectDiscoveryInfoView.as_view(),
173
name="oidc-connect-discovery-info",
174
),
175
# JWKS endpoint
176
path(".well-known/jwks.json", views.JwksInfoView.as_view(), name="jwks-info"),
177
# UserInfo endpoint
178
path("userinfo/", views.UserInfoView.as_view(), name="user-info"),
179
# Logout endpoint
180
path("logout/", views.RPInitiatedLogoutView.as_view(), name="rp-initiated-logout"),
181
]
182
```
183
184
## Usage Examples
185
186
### OIDC Discovery
187
188
```python
189
# Client discovers OIDC provider configuration
190
# GET /.well-known/openid-configuration
191
192
# Response:
193
# {
194
# "issuer": "https://example.com",
195
# "authorization_endpoint": "https://example.com/o/authorize/",
196
# "token_endpoint": "https://example.com/o/token/",
197
# "userinfo_endpoint": "https://example.com/o/userinfo/",
198
# "jwks_uri": "https://example.com/.well-known/jwks.json",
199
# "end_session_endpoint": "https://example.com/o/logout/",
200
# "scopes_supported": ["openid", "profile", "email"],
201
# "response_types_supported": ["code", "id_token", "code id_token"],
202
# "grant_types_supported": ["authorization_code", "implicit"],
203
# "subject_types_supported": ["public"],
204
# "id_token_signing_alg_values_supported": ["RS256", "HS256"],
205
# "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"]
206
# }
207
```
208
209
### OIDC Authorization Flow
210
211
```python
212
# 1. Authorization request with openid scope
213
# GET /o/authorize/?response_type=code&scope=openid+profile+email&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&nonce=NONCE&state=STATE
214
215
# 2. Token exchange (same as OAuth2 but includes ID token)
216
# POST /o/token/
217
# Content-Type: application/x-www-form-urlencoded
218
#
219
# grant_type=authorization_code&code=AUTH_CODE&redirect_uri=REDIRECT_URI&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
220
221
# Response includes ID token:
222
# {
223
# "access_token": "ACCESS_TOKEN",
224
# "token_type": "Bearer",
225
# "expires_in": 3600,
226
# "refresh_token": "REFRESH_TOKEN",
227
# "scope": "openid profile email",
228
# "id_token": "ID_TOKEN_JWT"
229
# }
230
```
231
232
### UserInfo Endpoint Usage
233
234
```python
235
# Request user information with access token
236
# GET /o/userinfo/
237
# Authorization: Bearer ACCESS_TOKEN
238
239
# Response:
240
# {
241
# "sub": "user123",
242
# "name": "John Doe",
243
# "given_name": "John",
244
# "family_name": "Doe",
245
# "email": "john.doe@example.com",
246
# "email_verified": true,
247
# "picture": "https://example.com/avatar.jpg"
248
# }
249
250
# Alternative POST method:
251
# POST /o/userinfo/
252
# Content-Type: application/x-www-form-urlencoded
253
# Authorization: Bearer ACCESS_TOKEN
254
```
255
256
### JWKS Endpoint
257
258
```python
259
# Get public keys for ID token verification
260
# GET /.well-known/jwks.json
261
262
# Response:
263
# {
264
# "keys": [
265
# {
266
# "kty": "RSA",
267
# "use": "sig",
268
# "kid": "key1",
269
# "n": "BASE64_MODULUS",
270
# "e": "AQAB"
271
# }
272
# ]
273
# }
274
```
275
276
### Logout Flow
277
278
```python
279
# 1. Client initiates logout
280
# GET /o/logout/?id_token_hint=ID_TOKEN&post_logout_redirect_uri=LOGOUT_URI&state=STATE
281
282
# 2. User confirms logout (or automatic if configured)
283
# POST /o/logout/
284
# Content-Type: application/x-www-form-urlencoded
285
#
286
# allow=true&id_token_hint=ID_TOKEN&post_logout_redirect_uri=LOGOUT_URI&state=STATE
287
288
# 3. Redirect to post_logout_redirect_uri
289
# HTTP/1.1 302 Found
290
# Location: LOGOUT_URI?state=STATE
291
```
292
293
### ID Token Handling
294
295
```python
296
import jwt
297
from oauth2_provider.models import get_application_model
298
299
def verify_id_token(id_token_jwt, client_id):
300
"""Verify and decode OIDC ID token"""
301
302
Application = get_application_model()
303
try:
304
application = Application.objects.get(client_id=client_id)
305
306
# Get signing key
307
if application.algorithm == Application.RS256_ALGORITHM:
308
# Use RSA public key
309
key = application.jwk_key
310
elif application.algorithm == Application.HS256_ALGORITHM:
311
# Use client secret
312
key = application.client_secret
313
else:
314
raise ValueError("No signing algorithm configured")
315
316
# Decode and verify ID token
317
payload = jwt.decode(
318
id_token_jwt,
319
key,
320
algorithms=[application.algorithm],
321
audience=client_id,
322
issuer="https://example.com" # Your issuer URL
323
)
324
325
return payload
326
327
except jwt.InvalidTokenError as e:
328
raise ValueError(f"Invalid ID token: {e}")
329
except Application.DoesNotExist:
330
raise ValueError("Unknown client")
331
```
332
333
### Custom UserInfo Claims
334
335
```python
336
from oauth2_provider.views.oidc import UserInfoView
337
from django.http import JsonResponse
338
339
class CustomUserInfoView(UserInfoView):
340
"""Custom UserInfo endpoint with additional claims"""
341
342
def get_userinfo_claims(self, token):
343
"""Add custom claims to UserInfo response"""
344
claims = super().get_userinfo_claims(token)
345
346
# Add custom user attributes
347
user = token.user
348
if user:
349
claims.update({
350
'custom_field': getattr(user, 'custom_field', None),
351
'department': getattr(user.profile, 'department', None) if hasattr(user, 'profile') else None,
352
'roles': list(user.groups.values_list('name', flat=True)),
353
'last_login': user.last_login.isoformat() if user.last_login else None,
354
})
355
356
return claims
357
358
# URL configuration
359
# path('userinfo/', CustomUserInfoView.as_view(), name='custom-user-info')
360
```
361
362
### OIDC Settings Configuration
363
364
```python
365
# settings.py
366
OAUTH2_PROVIDER = {
367
# OIDC settings
368
'OIDC_ENABLED': True,
369
'OIDC_RSA_PRIVATE_KEY': '''-----BEGIN RSA PRIVATE KEY-----
370
MIIEowIBAAKCAQEA...
371
-----END RSA PRIVATE KEY-----''',
372
373
# ID token settings
374
'ID_TOKEN_EXPIRE_SECONDS': 3600,
375
'OIDC_USERINFO_ENDPOINT_RESPONSE_TYPE': 'application/json',
376
377
# Standard OAuth2 scopes plus OIDC scopes
378
'SCOPES': {
379
'read': 'Read access',
380
'write': 'Write access',
381
'openid': 'OpenID Connect',
382
'profile': 'User profile information',
383
'email': 'Email address',
384
},
385
386
# OIDC issuer (your domain)
387
'OIDC_ISSUER': 'https://yourdomain.com',
388
}
389
```
390
391
### Hybrid Flow Example
392
393
```python
394
# OIDC Hybrid Flow (code + id_token)
395
# GET /o/authorize/?response_type=code+id_token&scope=openid+profile&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&nonce=NONCE
396
397
# Response includes both authorization code and ID token:
398
# HTTP/1.1 302 Found
399
# Location: REDIRECT_URI#code=AUTH_CODE&id_token=ID_TOKEN_JWT&state=STATE
400
401
# Client can immediately get user info from ID token
402
# and exchange code for access token and refresh token
403
```
404
405
### Error Handling
406
407
OIDC endpoints return standard OIDC error responses:
408
409
```python
410
# UserInfo errors:
411
# HTTP 401 Unauthorized - Invalid or missing access token
412
# HTTP 403 Forbidden - Token doesn't have 'openid' scope
413
#
414
# {
415
# "error": "invalid_token",
416
# "error_description": "The access token is invalid"
417
# }
418
419
# Logout errors:
420
# HTTP 400 Bad Request - Invalid logout request
421
#
422
# {
423
# "error": "invalid_request",
424
# "error_description": "Missing or invalid id_token_hint"
425
# }
426
427
# Discovery endpoint errors:
428
# HTTP 500 Internal Server Error - Server configuration issues
429
```