Working with Refresh Tokens

Note

The process for using refresh tokens is in the process of changing on Reddit’s end. This documentation has been updated to be aligned with the future of how Reddit handles refresh tokens, and will be the only supported method in PRAW 8+. For more information please see: https://old.reddit.com/r/redditdev/comments/kvzaot/oauth2_api_changes_upcoming/

Reddit OAuth2 Scopes

Before working with refresh tokens you should decide which scopes your application requires. If you want to use all scopes, you can use the special scope *.

To get an up-to-date listing of all Reddit scopes and their descriptions run the following:

import requests

response = requests.get(
    "https://www.reddit.com/api/v1/scopes.json",
    headers={"User-Agent": "fetch-scopes by u/bboe"}
)

for scope, data in sorted(response.json().items()):
    print(f"{scope:>18s}  {data['description']}")

As of February 2021, the available scopes are:

Scope

Description

account

Update preferences and related account information. Will not have access to your email or password.

creddits

Spend my reddit gold creddits on giving gold to other users.

edit

Edit and delete my comments and submissions.

flair

Select my subreddit flair. Change link flair on my submissions.

history

Access my voting history and comments or submissions I’ve saved or hidden.

identity

Access my reddit username and signup date.

livemanage

Manage settings and contributors of live threads I contribute to.

modconfig

Manage the configuration, sidebar, and CSS of subreddits I moderate.

modcontributors

Add/remove users to approved user lists and ban/unban or mute/unmute users from subreddits I moderate.

modflair

Manage and assign flair in subreddits I moderate.

modlog

Access the moderation log in subreddits I moderate.

modmail

Access and manage modmail via mod.reddit.com.

modothers

Invite or remove other moderators from subreddits I moderate.

modposts

Approve, remove, mark nsfw, and distinguish content in subreddits I moderate.

modself

Accept invitations to moderate a subreddit. Remove myself as a moderator or contributor of subreddits I moderate or contribute to.

modtraffic

Access traffic stats in subreddits I moderate.

modwiki

Change editors and visibility of wiki pages in subreddits I moderate.

mysubreddits

Access the list of subreddits I moderate, contribute to, and subscribe to.

privatemessages

Access my inbox and send private messages to other users.

read

Access posts and comments through my account.

report

Report content for rules violations. Hide & show individual submissions.

save

Save and unsave comments and submissions.

structuredstyles

Edit structured styles for a subreddit I moderate.

submit

Submit links and comments from my account.

subscribe

Manage my subreddit subscriptions. Manage “friends” - users whose content I follow.

vote

Submit and change my votes on comments and submissions.

wikiedit

Edit wiki pages on my behalf

wikiread

Read wiki pages through my account

Obtaining Refresh Tokens

The following program can be used to obtain a refresh token with the desired scopes:

#!/usr/bin/env python

"""This example demonstrates the flow for retrieving a refresh token.

This tool can be used to conveniently create refresh tokens for later use with your web
application OAuth2 credentials.

To create a Reddit application visit the following link while logged into the account
you want to create a refresh token for: https://www.reddit.com/prefs/apps/

Create a "web app" with the redirect uri set to: http://localhost:8080

After the application is created, take note of:

- REDDIT_CLIENT_ID; the line just under "web app" in the upper left of the Reddit
  Application
- REDDIT_CLIENT_SECRET; the value to the right of "secret"

Usage:

    EXPORT praw_client_id=<REDDIT_CLIENT_ID>
    EXPORT praw_client_secret=<REDDIT_CLIENT_SECRET>
    python3 obtain_refresh_token.py

"""
import random
import socket
import sys

import praw


def main():
    """Provide the program's entry point when directly executed."""
    scope_input = input(
        "Enter a comma separated list of scopes, or `*` for all scopes: "
    )
    scopes = [scope.strip() for scope in scope_input.strip().split(",")]

    reddit = praw.Reddit(
        redirect_uri="http://localhost:8080",
        user_agent="obtain_refresh_token/v0 by u/bboe",
    )
    state = str(random.randint(0, 65000))
    url = reddit.auth.url(scopes, state, "permanent")
    print(f"Now open this url in your browser: {url}")

    client = receive_connection()
    data = client.recv(1024).decode("utf-8")
    param_tokens = data.split(" ", 2)[1].split("?", 1)[1].split("&")
    params = {
        key: value for (key, value) in [token.split("=") for token in param_tokens]
    }

    if state != params["state"]:
        send_message(
            client,
            f"State mismatch. Expected: {state} Received: {params['state']}",
        )
        return 1
    elif "error" in params:
        send_message(client, params["error"])
        return 1

    refresh_token = reddit.auth.authorize(params["code"])
    send_message(client, f"Refresh token: {refresh_token}")
    return 0


def receive_connection():
    """Wait for and then return a connected socket..

    Opens a TCP connection on port 8080, and waits for a single client.

    """
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(("localhost", 8080))
    server.listen(1)
    client = server.accept()[0]
    server.close()
    return client


def send_message(client, message):
    """Send message to client and close the connection."""
    print(message)
    client.send(f"HTTP/1.1 200 OK\r\n\r\n{message}".encode("utf-8"))
    client.close()


if __name__ == "__main__":
    sys.exit(main())

Using and Updating Refresh Tokens

Reddit refresh tokens can be used only once. When an authorization is refreshed the existing refresh token is consumed and a new access token and refresh token will be issued. While PRAW automatically handles refreshing tokens when needed, it does not automatically handle the storage of the refresh tokens. However, PRAW provides the facilities for you to manage your refresh tokens via custom subclasses of BaseTokenManager. For trivial examples, PRAW provides the FileTokenManager.

The following program demonstrates how to prepare a file with an initial refresh token, and configure PRAW to both use that refresh token, and keep the file up-to-date with a valid refresh token.

#!/usr/bin/env python3
"""This example demonstrates using the file token manager for refresh tokens.

In order to run this program, you will first need to obtain a valid refresh token. You
can use the `obtain_refresh_token.py` example to help.

In this example, refresh tokens will be saved into a file `refresh_token.txt` relative
to your current working directory. If your current working directory is under version
control it is strongly encouraged you add `refresh_token.txt` to the version control
ignore list.

Usage:

    EXPORT praw_client_id=<REDDIT_CLIENT_ID>
    EXPORT praw_client_secret=<REDDIT_CLIENT_SECRET>
    python3 use_file_token_manager.py

"""
import os
import sys

import praw
from praw.util.token_manager import FileTokenManager

REFRESH_TOKEN_FILENAME = "refresh_token.txt"


def initialize_refresh_token_file():
    if os.path.isfile(REFRESH_TOKEN_FILENAME):
        return

    refresh_token = input("Initial refresh token value: ")
    with open(REFRESH_TOKEN_FILENAME, "w") as fp:
        fp.write(refresh_token)


def main():
    if "praw_client_id" not in os.environ:
        sys.stderr.write("Environment variable ``praw_client_id`` must be defined\n")
        return 1
    if "praw_client_secret" not in os.environ:
        sys.stderr.write(
            "Environment variable ``praw_client_secret`` must be defined\n"
        )
        return 1

    initialize_refresh_token_file()

    refresh_token_manager = FileTokenManager(REFRESH_TOKEN_FILENAME)
    reddit = praw.Reddit(
        token_manager=refresh_token_manager,
        user_agent="use_file_token_manager/v0 by u/bboe",
    )

    scopes = reddit.auth.scopes()
    if scopes == {"*"}:
        print(f"{reddit.user.me()} is authenticated with all scopes")
    elif "identity" in scopes:
        print(
            f"{reddit.user.me()} is authenticated with the following scopes: {scopes}"
        )
    else:
        print(f"You are authenticated with the following scopes: {scopes}")


if __name__ == "__main__":
    sys.exit(main())