OAuth

Logging in with OAuth instead of a password. For the prose on clients, scopes, session storage and token refresh, see OAuth.

Log in from the command line

A loopback client needs no hosted metadata. The session is kept in a file, so the browser opens only on the first run.

examples/oauth/loopback_cli.py
"""Log in from the command line with OAuth and make an authenticated request.

A loopback client needs no hosted client metadata: the authorization server accepts
``http://localhost`` as the client ID and redirects the browser back to a port on this machine.

The session is kept in ``oauth-session.json``. Run the script again to reuse it without the browser.

    python loopback_cli.py alice.bsky.social
"""

import sys
import webbrowser

from atproto import Client, JsonFileStore, OAuthClient, OAuthSession, models
from atproto.exceptions import OAuthError

# ask only for what the script does: create posts
SCOPE = f'atproto repo:{models.ids.AppBskyFeedPost}?action=create'
SESSION_FILE = 'oauth-session.json'


def open_browser(url: str) -> None:
    print('Opening the browser. If nothing happens, open this URL yourself:')
    print(url)
    webbrowser.open(url)


def get_session(oauth_client: OAuthClient, identifier: str) -> OAuthSession:
    try:
        return oauth_client.restore(identifier)
    except OAuthError:
        return oauth_client.login_with_browser(identifier, open_url=open_browser)


def main(identifier: str) -> None:
    oauth_client = OAuthClient.loopback(scope=SCOPE, session_store=JsonFileStore(SESSION_FILE))

    session = get_session(oauth_client, identifier)
    print(f'Logged in as {session.did} with scope: {session.get_token_info().scope}')

    client = Client()
    client.login_oauth(session)

    post = client.send_post('Hello from the OAuth example of the atproto Python SDK!')
    print(f'Posted: {post.uri}')


if __name__ == '__main__':
    if len(sys.argv) != 2:
        sys.exit(f'Usage: {sys.argv[0]} <handle or DID>')

    main(sys.argv[1])