0
# Authentication
1
2
PRAW provides comprehensive OAuth 2.0 authentication support for both script applications and web applications. It handles token management, refresh tokens, and supports multiple authentication flows while maintaining security and following Reddit's API guidelines.
3
4
## Capabilities
5
6
### Auth Class - Authentication Management
7
8
Handle OAuth 2.0 authentication flows and token management.
9
10
```python { .api }
11
class Auth:
12
def __init__(self, reddit): ...
13
14
def authorize(self, code: str):
15
"""
16
Complete OAuth 2.0 authorization with authorization code.
17
18
Parameters:
19
- code: Authorization code from Reddit OAuth callback
20
21
This method exchanges the authorization code for access and refresh tokens.
22
Used in web application authentication flow.
23
"""
24
25
def implicit(
26
self,
27
access_token: str,
28
expires_in: int,
29
scope: str
30
):
31
"""
32
Set implicit OAuth 2.0 authorization.
33
34
Parameters:
35
- access_token: Access token from implicit flow
36
- expires_in: Token expiration time in seconds
37
- scope: Space-separated list of granted scopes
38
39
Used for implicit grant flow (less secure, not recommended).
40
"""
41
42
def url(
43
self,
44
scopes: list,
45
state: str,
46
*,
47
duration: str = "temporary",
48
implicit: bool = False,
49
**kwargs
50
) -> str:
51
"""
52
Generate OAuth 2.0 authorization URL.
53
54
Parameters:
55
- scopes: List of requested scopes
56
- state: Random string to prevent CSRF attacks
57
- duration: "temporary" or "permanent" token duration
58
- implicit: Use implicit grant flow
59
60
Returns:
61
Authorization URL for user to visit
62
"""
63
64
def revoke_token(
65
self,
66
token: str = None,
67
*,
68
revoke_refresh: bool = True,
69
**kwargs
70
):
71
"""
72
Revoke access or refresh token.
73
74
Parameters:
75
- token: Token to revoke (defaults to current access token)
76
- revoke_refresh: Also revoke refresh token
77
78
Invalidates the specified token on Reddit's servers.
79
"""
80
81
def scopes(self) -> set:
82
"""
83
Get currently authorized scopes.
84
85
Returns:
86
Set of scope strings currently granted
87
"""
88
```
89
90
### Authentication Flows
91
92
#### Script Application Authentication
93
94
For personal scripts and automation tools running on your own machine.
95
96
```python { .api }
97
# Script authentication configuration
98
reddit = praw.Reddit(
99
client_id="your_client_id",
100
client_secret="your_client_secret",
101
username="your_username",
102
password="your_password",
103
user_agent="Script Name v1.0 by /u/yourusername"
104
)
105
106
# Authentication is automatic - no additional steps needed
107
# All scopes are automatically granted for script apps
108
```
109
110
#### Web Application Authentication
111
112
For web applications that need user authorization with specific scopes.
113
114
```python { .api }
115
# Step 1: Initialize Reddit instance for web app
116
reddit = praw.Reddit(
117
client_id="your_client_id",
118
client_secret="your_client_secret",
119
redirect_uri="http://localhost:8080/callback",
120
user_agent="Web App v1.0 by /u/yourusername"
121
)
122
123
# Step 2: Generate authorization URL
124
scopes = ["identity", "read", "submit", "edit"]
125
state = "random_string_for_csrf_protection"
126
auth_url = reddit.auth.url(scopes, state, duration="permanent")
127
128
# Step 3: Redirect user to auth_url, they authorize and return with code
129
130
# Step 4: Exchange code for tokens
131
reddit.auth.authorize(code_from_callback)
132
133
# Step 5: Reddit instance is now authenticated
134
authenticated_user = reddit.user.me()
135
```
136
137
#### Read-Only Authentication
138
139
For applications that only need to read public data.
140
141
```python { .api }
142
# Read-only mode (no user authentication required)
143
reddit = praw.Reddit(
144
client_id="your_client_id",
145
client_secret="your_client_secret",
146
user_agent="Read-Only App v1.0 by /u/yourusername"
147
)
148
149
# Enable read-only mode
150
reddit.read_only = True
151
152
# Can now access public content without user authentication
153
subreddit = reddit.subreddit("python")
154
for post in subreddit.hot(limit=10):
155
print(post.title)
156
```
157
158
### OAuth 2.0 Scopes
159
160
Reddit OAuth scopes define what actions your application can perform.
161
162
```python { .api }
163
# Available OAuth scopes
164
SCOPES = {
165
"identity": "Access user identity (username, karma, creation date)",
166
"edit": "Edit and delete user's comments and submissions",
167
"flair": "Manage user and link flair",
168
"history": "Access user's post and comment history",
169
"modconfig": "Manage configuration and sidebar of subreddits",
170
"modflair": "Manage flair templates and flair of other users",
171
"modlog": "Access moderation log",
172
"modposts": "Approve, remove, mark nsfw, and distinguish content",
173
"modwiki": "Change wiki settings and edit wiki pages",
174
"mysubreddits": "Access user's subscribed subreddits",
175
"privatemessages": "Access and send private messages",
176
"read": "Read posts and comments",
177
"report": "Report content for rule violations",
178
"save": "Save and unsave posts and comments",
179
"submit": "Submit posts and comments",
180
"subscribe": "Subscribe and unsubscribe from subreddits",
181
"vote": "Vote on posts and comments",
182
"wikiedit": "Edit wiki pages",
183
"wikiread": "Read wiki pages"
184
}
185
186
# Example: Request multiple scopes
187
scopes = ["identity", "read", "submit", "edit", "vote"]
188
auth_url = reddit.auth.url(scopes, state)
189
```
190
191
### Token Management
192
193
#### Refresh Token Handling
194
195
PRAW automatically handles token refresh when using permanent tokens.
196
197
```python { .api }
198
# Automatic token refresh (handled internally by PRAW)
199
# When access token expires, PRAW uses refresh token automatically
200
201
# Check current scopes
202
current_scopes = reddit.auth.scopes()
203
print(f"Authorized scopes: {current_scopes}")
204
205
# Manually revoke tokens if needed
206
reddit.auth.revoke_token() # Revoke current access token
207
reddit.auth.revoke_token(revoke_refresh=True) # Also revoke refresh token
208
```
209
210
#### Custom Token Managers
211
212
Implement custom token storage and retrieval mechanisms.
213
214
```python { .api }
215
class BaseTokenManager:
216
"""Abstract base class for token managers."""
217
218
def post_refresh_callback(self, authorizer):
219
"""Called after token refresh."""
220
pass
221
222
def pre_refresh_callback(self, authorizer):
223
"""Called before token refresh."""
224
pass
225
226
class FileTokenManager(BaseTokenManager):
227
"""File-based token storage."""
228
229
def __init__(self, filename: str): ...
230
231
class SQLiteTokenManager(BaseTokenManager):
232
"""SQLite-based token storage."""
233
234
def __init__(self, database: str, key: str): ...
235
236
# Use custom token manager
237
token_manager = FileTokenManager("tokens.json")
238
reddit = praw.Reddit(
239
client_id="your_client_id",
240
client_secret="your_client_secret",
241
redirect_uri="your_redirect_uri",
242
user_agent="your_user_agent",
243
token_manager=token_manager
244
)
245
```
246
247
### Configuration Management
248
249
#### Environment Variables
250
251
Configure authentication using environment variables for security.
252
253
```python { .api }
254
# Set environment variables
255
# praw_client_id=your_client_id
256
# praw_client_secret=your_client_secret
257
# praw_username=your_username
258
# praw_password=your_password
259
# praw_user_agent=your_user_agent
260
261
# PRAW automatically uses environment variables
262
reddit = praw.Reddit()
263
264
# Or specify which environment variables to use
265
reddit = praw.Reddit(
266
client_id=os.environ["MY_CLIENT_ID"],
267
client_secret=os.environ["MY_CLIENT_SECRET"],
268
username=os.environ["MY_USERNAME"],
269
password=os.environ["MY_PASSWORD"],
270
user_agent=os.environ["MY_USER_AGENT"]
271
)
272
```
273
274
#### Configuration Files
275
276
Use praw.ini configuration files for different environments.
277
278
```python { .api }
279
# praw.ini file format
280
"""
281
[DEFAULT]
282
user_agent=MyBot v1.0 by /u/yourusername
283
284
[development]
285
client_id=dev_client_id
286
client_secret=dev_client_secret
287
username=dev_username
288
password=dev_password
289
290
[production]
291
client_id=prod_client_id
292
client_secret=prod_client_secret
293
username=prod_username
294
password=prod_password
295
"""
296
297
# Load specific configuration
298
reddit = praw.Reddit("development") # Uses [development] section
299
reddit = praw.Reddit("production") # Uses [production] section
300
301
# Override specific settings
302
reddit = praw.Reddit(
303
"development",
304
username="override_username" # Override just username
305
)
306
```
307
308
### Authentication State Management
309
310
Check authentication status and handle authentication errors.
311
312
```python { .api }
313
# Check if authenticated
314
try:
315
user = reddit.user.me()
316
print(f"Authenticated as: {user.name}")
317
is_authenticated = True
318
except AttributeError:
319
print("Not authenticated")
320
is_authenticated = False
321
322
# Check read-only mode
323
if reddit.read_only:
324
print("In read-only mode")
325
else:
326
print("In authenticated mode")
327
328
# Get current scopes
329
if not reddit.read_only:
330
scopes = reddit.auth.scopes()
331
print(f"Available scopes: {scopes}")
332
333
# Check for specific scope
334
if "submit" in scopes:
335
print("Can submit posts")
336
if "modposts" in scopes:
337
print("Can moderate posts")
338
```
339
340
### Error Handling
341
342
Handle authentication-related errors appropriately.
343
344
```python { .api }
345
from praw.exceptions import (
346
InvalidImplicitAuth,
347
ReadOnlyException,
348
RedditAPIException
349
)
350
351
try:
352
# Attempt authenticated operation
353
reddit.subreddit("test").submit("title", selftext="content")
354
355
except ReadOnlyException:
356
print("Cannot submit in read-only mode")
357
358
except InvalidImplicitAuth:
359
print("Invalid implicit authentication")
360
361
except RedditAPIException as e:
362
# Handle specific Reddit API errors
363
for error in e.items:
364
if error.error_type == "NO_PERMISSION":
365
print("Insufficient permissions")
366
elif error.error_type == "INVALID_CREDENTIALS":
367
print("Invalid authentication credentials")
368
```
369
370
## Usage Examples
371
372
### Script Authentication Setup
373
374
```python
375
import praw
376
import os
377
378
# Method 1: Direct configuration
379
reddit = praw.Reddit(
380
client_id="your_client_id",
381
client_secret="your_client_secret",
382
username="your_username",
383
password="your_password",
384
user_agent="MyScript v1.0 by /u/yourusername"
385
)
386
387
# Method 2: Environment variables
388
reddit = praw.Reddit(
389
client_id=os.environ["PRAW_CLIENT_ID"],
390
client_secret=os.environ["PRAW_CLIENT_SECRET"],
391
username=os.environ["PRAW_USERNAME"],
392
password=os.environ["PRAW_PASSWORD"],
393
user_agent=os.environ["PRAW_USER_AGENT"]
394
)
395
396
# Verify authentication
397
me = reddit.user.me()
398
print(f"Authenticated as: {me.name}")
399
```
400
401
### Web Application Authentication
402
403
```python
404
import praw
405
from flask import Flask, request, redirect, session
406
import secrets
407
408
app = Flask(__name__)
409
app.secret_key = "your-secret-key"
410
411
reddit = praw.Reddit(
412
client_id="your_client_id",
413
client_secret="your_client_secret",
414
redirect_uri="http://localhost:5000/callback",
415
user_agent="WebApp v1.0 by /u/yourusername"
416
)
417
418
@app.route("/login")
419
def login():
420
# Generate state for CSRF protection
421
state = secrets.token_urlsafe(32)
422
session["oauth_state"] = state
423
424
# Request scopes
425
scopes = ["identity", "read", "submit"]
426
427
# Generate authorization URL
428
auth_url = reddit.auth.url(scopes, state, duration="permanent")
429
return redirect(auth_url)
430
431
@app.route("/callback")
432
def callback():
433
# Verify state parameter
434
if request.args.get("state") != session.get("oauth_state"):
435
return "Invalid state parameter", 400
436
437
# Get authorization code
438
code = request.args.get("code")
439
if not code:
440
return "No authorization code received", 400
441
442
# Exchange code for tokens
443
reddit.auth.authorize(code)
444
445
# Get authenticated user
446
user = reddit.user.me()
447
session["reddit_username"] = user.name
448
449
return f"Successfully authenticated as {user.name}"
450
451
@app.route("/protected")
452
def protected():
453
if "reddit_username" not in session:
454
return redirect("/login")
455
456
# Use authenticated Reddit instance
457
username = session["reddit_username"]
458
user = reddit.redditor(username)
459
460
return f"Hello {username}! Your karma: {user.comment_karma}"
461
```
462
463
### Token Management Example
464
465
```python
466
import praw
467
from praw.util.token_manager import FileTokenManager
468
469
# Use file-based token storage
470
token_manager = FileTokenManager("reddit_tokens.json")
471
472
reddit = praw.Reddit(
473
client_id="your_client_id",
474
client_secret="your_client_secret",
475
redirect_uri="your_redirect_uri",
476
user_agent="your_user_agent",
477
token_manager=token_manager
478
)
479
480
# First time: perform OAuth flow
481
if not hasattr(reddit.auth, 'refresh_token'):
482
scopes = ["identity", "read", "submit"]
483
state = "random_state"
484
auth_url = reddit.auth.url(scopes, state)
485
print(f"Visit: {auth_url}")
486
487
code = input("Enter authorization code: ")
488
reddit.auth.authorize(code)
489
490
# Subsequent runs: tokens are loaded automatically
491
user = reddit.user.me()
492
print(f"Authenticated as: {user.name}")
493
494
# Check authorized scopes
495
scopes = reddit.auth.scopes()
496
print(f"Authorized scopes: {scopes}")
497
498
# Revoke tokens when done
499
reddit.auth.revoke_token(revoke_refresh=True)
500
```
501
502
## Types
503
504
```python { .api }
505
class Config:
506
"""PRAW configuration management."""
507
508
CONFIG_NOT_SET: str # Sentinel value for unset configuration
509
510
def __init__(
511
self,
512
site_name: str = None,
513
interpolation: str = None,
514
**settings
515
): ...
516
517
class InvalidImplicitAuth(Exception):
518
"""Raised when implicit authentication is used incorrectly."""
519
520
class ReadOnlyException(Exception):
521
"""Raised when attempting write operations in read-only mode."""
522
523
class InvalidURL(Exception):
524
"""Raised when an invalid URL is encountered."""
525
526
class MissingRequiredAttributeException(Exception):
527
"""Raised when required configuration is missing."""
528
```