or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

drf-integration.mdindex.mdmanagement-commands.mdmanagement-views.mdmodels.mdoauth2-endpoints.mdoidc.mdsettings.mdview-protection.md

oidc.mddocs/

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

```