or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication-permissions.mdexceptions-status.mdfields-relations.mdindex.mdpagination-filtering.mdrequests-responses.mdrouters-urls.mdserializers.mdviews-viewsets.md

authentication-permissions.mddocs/

0

# Authentication & Permissions

1

2

Django REST Framework provides a comprehensive authentication and permissions system with full type safety. The type stubs enable precise type checking for security configurations, custom authentication backends, and permission logic.

3

4

## Authentication Classes

5

6

### BaseAuthentication

7

8

```python { .api }

9

class BaseAuthentication:

10

"""Base class for all authentication backends."""

11

12

def authenticate(self, request: Request) -> tuple[Any, Any] | None:

13

"""

14

Authenticate the request and return a two-tuple of (user, token).

15

16

Returns:

17

tuple[Any, Any] | None: (user, auth) or None if not authenticated

18

"""

19

...

20

21

def authenticate_header(self, request: Request) -> str | None:

22

"""

23

Return a string to be used as the value of the WWW-Authenticate

24

header in a 401 Unauthenticated response.

25

26

Returns:

27

str | None: Authentication header value or None

28

"""

29

...

30

```

31

32

### SessionAuthentication

33

34

```python { .api }

35

class SessionAuthentication(BaseAuthentication):

36

"""Django session-based authentication."""

37

38

def authenticate(self, request: Request) -> tuple[Any, Any] | None: ...

39

def authenticate_header(self, request: Request) -> str | None: ...

40

def enforce_csrf(self, request: Request) -> None: ...

41

```

42

43

### BasicAuthentication

44

45

```python { .api }

46

class BasicAuthentication(BaseAuthentication):

47

"""HTTP Basic authentication against username/password."""

48

49

www_authenticate_realm: str

50

51

def authenticate(self, request: Request) -> tuple[Any, Any] | None: ...

52

def authenticate_header(self, request: Request) -> str: ...

53

def authenticate_credentials(

54

self,

55

userid: str,

56

password: str,

57

request: Request | None = None

58

) -> tuple[Any, None]: ...

59

```

60

61

**Parameters:**

62

- `www_authenticate_realm: str` - Realm for WWW-Authenticate header

63

- `userid: str` - Username from Basic auth header

64

- `password: str` - Password from Basic auth header

65

- `request: Request | None` - Current request object

66

67

### TokenAuthentication

68

69

```python { .api }

70

class TokenAuthentication(BaseAuthentication):

71

"""Token-based authentication using DRF tokens."""

72

73

keyword: str # Default: 'Token'

74

model: type[Model] | None

75

76

def authenticate(self, request: Request) -> tuple[Any, Any] | None: ...

77

def authenticate_header(self, request: Request) -> str: ...

78

def authenticate_credentials(self, key: str) -> tuple[Any, Any]: ...

79

def get_model(self) -> type[Model]: ...

80

```

81

82

**Parameters:**

83

- `keyword: str` - Token keyword in Authorization header (default: 'Token')

84

- `model: type[Model] | None` - Token model class

85

- `key: str` - Token key from Authorization header

86

87

### RemoteUserAuthentication

88

89

```python { .api }

90

class RemoteUserAuthentication(BaseAuthentication):

91

"""Authentication for users authenticated by external systems."""

92

93

header: str # Default: 'REMOTE_USER'

94

95

def authenticate(self, request: Request) -> tuple[Any, Any] | None: ...

96

def clean_username(self, username: str) -> str: ...

97

```

98

99

**Parameters:**

100

- `header: str` - HTTP header containing remote username

101

102

## Authentication Utilities

103

104

### Helper Functions

105

106

```python { .api }

107

def get_authorization_header(request: Request) -> bytes:

108

"""

109

Extract the authorization header from the request.

110

111

Args:

112

request: The DRF Request object

113

114

Returns:

115

bytes: Authorization header value

116

"""

117

...

118

```

119

120

### CSRF Protection

121

122

```python { .api }

123

class CSRFCheck(CsrfViewMiddleware):

124

"""CSRF protection middleware for DRF."""

125

126

def reject(self, request: HttpRequest, reason: str) -> None: ...

127

def process_view(

128

self,

129

request: HttpRequest,

130

callback: Callable,

131

callback_args: tuple,

132

callback_kwargs: dict[str, Any]

133

) -> HttpResponse | None: ...

134

```

135

136

## Permission Classes

137

138

### BasePermission

139

140

```python { .api }

141

class BasePermission:

142

"""Base class for all permission checks."""

143

144

def has_permission(self, request: Request, view: APIView) -> bool:

145

"""

146

Return True if permission is granted for the view.

147

148

Args:

149

request: The DRF request object

150

view: The view being accessed

151

152

Returns:

153

bool: True if permission granted

154

"""

155

...

156

157

def has_object_permission(

158

self,

159

request: Request,

160

view: APIView,

161

obj: Any

162

) -> bool:

163

"""

164

Return True if permission is granted for the object.

165

166

Args:

167

request: The DRF request object

168

view: The view being accessed

169

obj: The object being accessed

170

171

Returns:

172

bool: True if permission granted

173

"""

174

...

175

```

176

177

### Built-in Permission Classes

178

179

```python { .api }

180

class AllowAny(BasePermission):

181

"""Allow unrestricted access, regardless of authentication."""

182

pass

183

184

class IsAuthenticated(BasePermission):

185

"""Allow access only to authenticated users."""

186

pass

187

188

class IsAdminUser(BasePermission):

189

"""Allow access only to admin users."""

190

pass

191

192

class IsAuthenticatedOrReadOnly(BasePermission):

193

"""Allow read-only access to any user, write access to authenticated users."""

194

pass

195

```

196

197

### Django Model Permissions

198

199

```python { .api }

200

class DjangoModelPermissions(BasePermission):

201

"""Permission class that ties into Django's model permissions system."""

202

203

authenticated_users_only: bool

204

perms_map: dict[str, list[str]]

205

206

def get_required_permissions(

207

self,

208

method: str,

209

model_cls: type[Model]

210

) -> list[str]: ...

211

def get_required_object_permissions(

212

self,

213

method: str,

214

model_cls: type[Model]

215

) -> list[str]: ...

216

217

class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):

218

"""Django model permissions with anonymous read-only access."""

219

220

authenticated_users_only: bool

221

222

class DjangoObjectPermissions(DjangoModelPermissions):

223

"""Django object-level permissions."""

224

pass

225

```

226

227

**Parameters:**

228

- `authenticated_users_only: bool` - Require authentication for all access

229

- `perms_map: dict[str, list[str]]` - Mapping of HTTP methods to required permissions

230

231

## Permission Operators

232

233

### Logical Operators

234

235

```python { .api }

236

class AND:

237

"""Logical AND operator for permissions."""

238

239

def __init__(self, op1: BasePermission, op2: BasePermission) -> None: ...

240

def has_permission(self, request: Request, view: APIView) -> bool: ...

241

def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool: ...

242

243

class OR:

244

"""Logical OR operator for permissions."""

245

246

def __init__(self, op1: BasePermission, op2: BasePermission) -> None: ...

247

def has_permission(self, request: Request, view: APIView) -> bool: ...

248

def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool: ...

249

250

class NOT:

251

"""Logical NOT operator for permissions."""

252

253

def __init__(self, op1: BasePermission) -> None: ...

254

def has_permission(self, request: Request, view: APIView) -> bool: ...

255

def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool: ...

256

```

257

258

### Permission Composition

259

260

```python { .api }

261

# Combine permissions with operators

262

permission_classes = [IsAuthenticated & (IsOwner | IsAdmin)]

263

permission_classes = [IsAuthenticated & ~IsBlocked]

264

permission_classes = [(IsAuthenticated & IsOwner) | IsAdmin]

265

```

266

267

## Usage Examples

268

269

### Custom Authentication Backend

270

271

```python { .api }

272

from rest_framework.authentication import BaseAuthentication

273

from rest_framework.exceptions import AuthenticationFailed

274

from django.contrib.auth.models import User

275

276

class APIKeyAuthentication(BaseAuthentication):

277

"""Custom API key authentication."""

278

279

def authenticate(self, request: Request) -> tuple[User, str] | None:

280

api_key = request.META.get('HTTP_X_API_KEY')

281

if not api_key:

282

return None

283

284

try:

285

# Look up user by API key

286

profile = UserProfile.objects.select_related('user').get(api_key=api_key)

287

return (profile.user, api_key)

288

except UserProfile.DoesNotExist:

289

raise AuthenticationFailed('Invalid API key')

290

291

def authenticate_header(self, request: Request) -> str:

292

return 'X-API-Key'

293

294

class JWTAuthentication(BaseAuthentication):

295

"""JWT token authentication."""

296

297

def authenticate(self, request: Request) -> tuple[User, dict[str, Any]] | None:

298

auth_header = request.META.get('HTTP_AUTHORIZATION')

299

if not auth_header or not auth_header.startswith('Bearer '):

300

return None

301

302

token = auth_header[7:] # Remove 'Bearer ' prefix

303

304

try:

305

payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])

306

user = User.objects.get(id=payload['user_id'])

307

return (user, payload)

308

except (jwt.DecodeError, jwt.ExpiredSignatureError, User.DoesNotExist):

309

raise AuthenticationFailed('Invalid token')

310

311

def authenticate_header(self, request: Request) -> str:

312

return 'Bearer'

313

```

314

315

### Custom Permission Classes

316

317

```python { .api }

318

from rest_framework.permissions import BasePermission

319

320

class IsOwnerOrReadOnly(BasePermission):

321

"""Allow owners to edit, others to read only."""

322

323

def has_object_permission(

324

self,

325

request: Request,

326

view: APIView,

327

obj: Any

328

) -> bool:

329

# Read permissions for any request

330

if request.method in ['GET', 'HEAD', 'OPTIONS']:

331

return True

332

333

# Write permissions only to owner

334

return obj.owner == request.user

335

336

class IsSuperUserOrOwner(BasePermission):

337

"""Allow superusers or object owners full access."""

338

339

def has_permission(self, request: Request, view: APIView) -> bool:

340

return request.user and request.user.is_authenticated

341

342

def has_object_permission(

343

self,

344

request: Request,

345

view: APIView,

346

obj: Any

347

) -> bool:

348

return (

349

request.user.is_superuser or

350

getattr(obj, 'user', None) == request.user or

351

getattr(obj, 'owner', None) == request.user

352

)

353

354

class IsInGroupOrReadOnly(BasePermission):

355

"""Allow group members to edit, others to read only."""

356

357

def __init__(self, group_name: str) -> None:

358

self.group_name = group_name

359

360

def has_permission(self, request: Request, view: APIView) -> bool:

361

if request.method in ['GET', 'HEAD', 'OPTIONS']:

362

return True

363

364

return (

365

request.user.is_authenticated and

366

request.user.groups.filter(name=self.group_name).exists()

367

)

368

369

class HasModelPermission(BasePermission):

370

"""Check Django model permissions."""

371

372

def __init__(self, model: type[Model], action: str) -> None:

373

self.model = model

374

self.action = action

375

376

def has_permission(self, request: Request, view: APIView) -> bool:

377

if not request.user.is_authenticated:

378

return False

379

380

permission_name = f"{self.model._meta.app_label}.{self.action}_{self.model._meta.model_name}"

381

return request.user.has_perm(permission_name)

382

```

383

384

### View-Level Authentication Configuration

385

386

```python { .api }

387

from rest_framework.views import APIView

388

from rest_framework.authentication import (

389

SessionAuthentication,

390

TokenAuthentication,

391

BasicAuthentication

392

)

393

from rest_framework.permissions import IsAuthenticated, IsAdminUser

394

395

class MultiAuthView(APIView):

396

"""View with multiple authentication methods."""

397

398

authentication_classes = [

399

SessionAuthentication,

400

TokenAuthentication,

401

BasicAuthentication

402

]

403

permission_classes = [IsAuthenticated]

404

405

def get(self, request: Request) -> Response:

406

# User is guaranteed to be authenticated

407

return Response({

408

'user': request.user.username,

409

'auth_method': type(request.auth).__name__

410

})

411

412

class AdminOnlyView(APIView):

413

"""View restricted to admin users."""

414

415

authentication_classes = [SessionAuthentication, TokenAuthentication]

416

permission_classes = [IsAuthenticated, IsAdminUser]

417

418

def get(self, request: Request) -> Response:

419

# User is guaranteed to be authenticated admin

420

return Response({'message': 'Admin access granted'})

421

422

class DynamicPermissionView(APIView):

423

"""View with dynamic permission checking."""

424

425

def get_permissions(self) -> list[BasePermission]:

426

if self.request.method == 'GET':

427

permission_classes = [AllowAny]

428

elif self.request.method == 'POST':

429

permission_classes = [IsAuthenticated]

430

else:

431

permission_classes = [IsAuthenticated, IsAdminUser]

432

433

return [permission() for permission in permission_classes]

434

```

435

436

### ViewSet Authentication & Permissions

437

438

```python { .api }

439

from rest_framework import viewsets

440

from rest_framework.decorators import action

441

442

class BookViewSet(viewsets.ModelViewSet[Book]):

443

"""ViewSet with action-specific permissions."""

444

445

queryset = Book.objects.all()

446

serializer_class = BookSerializer

447

448

def get_permissions(self) -> list[BasePermission]:

449

"""Different permissions for different actions."""

450

if self.action == 'list':

451

permission_classes = [AllowAny]

452

elif self.action in ['create', 'update', 'partial_update']:

453

permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]

454

elif self.action == 'destroy':

455

permission_classes = [IsAuthenticated, IsOwnerOrReadOnly, IsAdminUser]

456

else:

457

permission_classes = [IsAuthenticated]

458

459

return [permission() for permission in permission_classes]

460

461

def get_authenticators(self) -> list[BaseAuthentication]:

462

"""Different authentication for different actions."""

463

if self.action in ['list', 'retrieve']:

464

# Public actions allow multiple auth methods

465

return [

466

SessionAuthentication(),

467

TokenAuthentication(),

468

BasicAuthentication()

469

]

470

else:

471

# Sensitive actions require token or session auth only

472

return [

473

SessionAuthentication(),

474

TokenAuthentication()

475

]

476

477

@action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])

478

def my_books(self, request: Request) -> Response:

479

"""Get current user's books."""

480

books = Book.objects.filter(owner=request.user)

481

serializer = self.get_serializer(books, many=True)

482

return Response(serializer.data)

483

484

@action(detail=True, methods=['post'], permission_classes=[IsAuthenticated, IsOwnerOrReadOnly])

485

def publish(self, request: Request, pk: str | None = None) -> Response:

486

"""Publish a book."""

487

book = self.get_object()

488

book.is_published = True

489

book.published_date = timezone.now()

490

book.save()

491

return Response({'status': 'published'})

492

```

493

494

### Permission Decorators

495

496

```python { .api }

497

from rest_framework.decorators import (

498

api_view,

499

authentication_classes,

500

permission_classes

501

)

502

503

@api_view(['GET'])

504

@permission_classes([AllowAny])

505

def public_endpoint(request: Request) -> Response:

506

"""Publicly accessible endpoint."""

507

return Response({'message': 'Public data'})

508

509

@api_view(['POST'])

510

@authentication_classes([TokenAuthentication])

511

@permission_classes([IsAuthenticated])

512

def protected_endpoint(request: Request) -> Response:

513

"""Protected endpoint requiring token auth."""

514

return Response({

515

'message': f'Hello {request.user.username}',

516

'user_id': request.user.id

517

})

518

519

@api_view(['GET', 'POST'])

520

@authentication_classes([SessionAuthentication, TokenAuthentication])

521

@permission_classes([IsAuthenticated])

522

def user_profile(request: Request) -> Response:

523

"""User profile endpoint with multiple auth methods."""

524

if request.method == 'GET':

525

serializer = UserSerializer(request.user)

526

return Response(serializer.data)

527

elif request.method == 'POST':

528

serializer = UserSerializer(request.user, data=request.data, partial=True)

529

if serializer.is_valid():

530

serializer.save()

531

return Response(serializer.data)

532

return Response(serializer.errors, status=400)

533

```

534

535

## Token Authentication Setup

536

537

### Token Model

538

539

```python { .api }

540

from rest_framework.authtoken.models import Token

541

542

# Token model fields

543

class Token(models.Model):

544

key: models.CharField

545

user: models.OneToOneField

546

created: models.DateTimeField

547

548

@classmethod

549

def generate_key(cls) -> str: ...

550

551

class TokenProxy(Token):

552

"""Proxy model for Token."""

553

554

class Meta:

555

proxy = True

556

```

557

558

### Token Views

559

560

```python { .api }

561

from rest_framework.authtoken.views import ObtainAuthToken

562

from rest_framework.authtoken import views

563

564

class ObtainAuthToken(APIView):

565

"""View for obtaining authentication tokens."""

566

567

serializer_class: type[Serializer]

568

569

def post(self, request: Request, *args: Any, **kwargs: Any) -> Response: ...

570

def get_serializer_context(self) -> dict[str, Any]: ...

571

def get_serializer(self, *args: Any, **kwargs: Any) -> Serializer: ...

572

573

# Pre-configured view instance

574

obtain_auth_token: Callable[..., Response]

575

```

576

577

### Token Serializer

578

579

```python { .api }

580

from rest_framework.authtoken.serializers import AuthTokenSerializer

581

582

class AuthTokenSerializer(serializers.Serializer):

583

"""Serializer for token authentication."""

584

585

username: serializers.CharField

586

password: serializers.CharField

587

token: serializers.CharField

588

589

def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: ...

590

```

591

592

## Security Best Practices

593

594

### Secure Authentication Configuration

595

596

```python { .api }

597

# Production-ready authentication setup

598

class SecureAPIView(APIView):

599

"""Secure API view configuration."""

600

601

authentication_classes = [

602

TokenAuthentication, # Primary auth method

603

SessionAuthentication # Fallback for web UI

604

]

605

permission_classes = [IsAuthenticated]

606

607

def dispatch(self, request: Request, *args: Any, **kwargs: Any) -> Response:

608

# Add security headers

609

response = super().dispatch(request, *args, **kwargs)

610

response['X-Content-Type-Options'] = 'nosniff'

611

response['X-Frame-Options'] = 'DENY'

612

response['X-XSS-Protection'] = '1; mode=block'

613

return response

614

```

615

616

### Rate Limiting with Permissions

617

618

```python { .api }

619

from rest_framework.throttling import UserRateThrottle

620

621

class RateLimitedPermission(BasePermission):

622

"""Permission that includes rate limiting."""

623

624

def has_permission(self, request: Request, view: APIView) -> bool:

625

# Check base permission

626

if not request.user.is_authenticated:

627

return False

628

629

# Apply rate limiting

630

throttle = UserRateThrottle()

631

if not throttle.allow_request(request, view):

632

return False

633

634

return True

635

```

636

637

This comprehensive authentication and permissions system provides type-safe security controls with full mypy support, enabling confident implementation of authentication backends, permission logic, and access control patterns.