NAME

Net::Async::WebSearch::Provider::Reddit::OAuth - Reddit search provider using the OAuth2 endpoint

VERSION

version 0.002

SYNOPSIS

# App-only OAuth (read-only, no user account involved) — simplest path:
my $r = Net::Async::WebSearch::Provider::Reddit::OAuth->new(
  client_id     => $ENV{REDDIT_CLIENT_ID},
  client_secret => $ENV{REDDIT_CLIENT_SECRET},
  user_agent    => 'my-search-bot/1.0 by /u/myredditname',
);

# Authorization-code flow — drive the consent dance from a host app
# (MCP server, web app, CLI…) where a human can visit a URL.
my $r = Net::Async::WebSearch::Provider::Reddit::OAuth->new(
  client_id     => ...,
  client_secret => ...,
  grant_type    => 'authorization_code',
  user_agent    => 'my-app/1.0',
  on_token_refresh => sub {
    my ( $provider, $raw ) = @_;
    # persist $provider->token_state to your session store
  },
);
$loop->add($ws_with_r);

# 1. Send the human off to authorize:
my $url = $r->authorize_url(
  redirect_uri => 'https://my.app/oauth/reddit/callback',
  scope        => [qw( read identity )],
  duration     => 'permanent',   # get a refresh_token
  state        => 'abc-123',     # CSRF guard you verify on callback
);

# 2. Human clicks Approve, callback receives ?code=...&state=...
#    Exchange it:
my $tokens = $r->complete_authorization(
  http         => $ws->http,
  code         => $code_from_callback,
  redirect_uri => 'https://my.app/oauth/reddit/callback',
)->get;

# 3. Later sessions: re-hydrate from a stored refresh_token.
my $r = Net::Async::WebSearch::Provider::Reddit::OAuth->new(
  client_id     => ..., client_secret => ...,
  grant_type    => 'authorization_code',
  refresh_token => $stored_refresh_token,
);

# Script app tied to a specific account (slightly higher rate limits):
my $r = Net::Async::WebSearch::Provider::Reddit::OAuth->new(
  client_id     => ...,
  client_secret => ...,
  grant_type    => 'password',
  username      => 'myaccount',
  password      => 'hunter2',
  user_agent    => 'my-search-bot/1.0 by /u/myaccount',
);

# Installed-app grant (no server-side secret — still set client_secret to ''):
my $r = Net::Async::WebSearch::Provider::Reddit::OAuth->new(
  client_id     => ...,
  client_secret => '',
  grant_type    => 'installed',
  device_id     => 'a-random-20-to-30-char-string',
  user_agent    => 'my-search-bot/1.0',
);

DESCRIPTION

Drop-in replacement for Net::Async::WebSearch::Provider::Reddit that hits the oauth.reddit.com endpoint with a bearer token. Higher rate limits, proper ToS compliance, same result shape.

client_id

Required. The app's public client id.

client_secret

Required (but may be the empty string for installed apps).

grant_type

client_credentials (default, app-only), password (script apps), installed (device-id apps), or authorization_code (user consent flow, typically driven by a host app — MCP server, web app, CLI — via "authorize_url" and "complete_authorization").

username

password

Reddit account credentials. Only used when grant_type=password.

device_id

20-30 character identifier for grant_type=installed. Should be stable per device but opaque. Defaults to DO_NOT_TRACK_THIS_DEVICE (a Reddit convention that opts out of fingerprinting).

user_agent

Override the User-Agent string. Strongly recommended — see "SETUP".

token_url

Override the token endpoint. Default https://www.reddit.com/api/v1/access_token.

token_margin

Seconds before token expiry to refresh preemptively. Default 60.

endpoint

Override the search endpoint. Default https://oauth.reddit.com.

All attributes inherited from Net::Async::WebSearch::Provider::Reddit (subreddit, sort, time) apply as well.

access_token

refresh_token

token_expires_at

Optional — pre-seed the auth state on new() (e.g. rehydrating a session from a host app's persistent store). When a refresh_token is present, the provider will use it to get fresh access tokens automatically instead of re-doing client_credentials.

on_token_refresh

Coderef called as $cb->($provider, $raw_response_hash) every time a new access token is minted (initial fetch or refresh). Use it to persist $provider->token_state to your session store so a later process can re-hydrate without re-prompting the user.

authorize_url(%args)

Builds — without a network round-trip — the Reddit URL the human should visit to grant your app access. Returns a plain URL string.

Required: redirect_uri. Optional: scope (arrayref or space-separated string, default 'read'), state (auto-generated if omitted — strongly recommended to supply your own for CSRF defence), duration (permanent (default) to receive a refresh_token, temporary for a one-hour token only).

This is the primitive a host app (MCP server, web app) uses to kick off the consent dance. The host shows the URL to the user — via a tool result, a browser redirect, or whatever — and waits for the ?code=... to come back on the callback URL.

complete_authorization(%args)

Exchanges an authorization code for an access/refresh token pair. Required: http (a Net::Async::HTTP — typically $ws->http), code (the value from the redirect), and redirect_uri (must match the one used in "authorize_url"). Returns a Future of the access token; also populates $self->access_token, $self->refresh_token, and $self->token_expires_at, and fires on_token_refresh if set.

token_state

Snapshots the currently-held auth state as a hashref:

{ access_token, refresh_token, token_expires_at }

Feed this hash back to a future new() call (or drop its keys in as access_token = ..., refresh_token => ..., token_expires_at => ...>) to continue the same authenticated session. Intended for host-app session persistence — this library does not persist anything on its own.

Same contract as the parent provider — plus a transparent token fetch/refresh round-trip before the first search (and again when the cached token is about to expire). For authorization_code grants without a seeded refresh_token, the caller must drive "authorize_url" / "complete_authorization" first; otherwise the first search will fail.

SETUP

Reddit apps are created at https://www.reddit.com/prefs/apps. The flow:

  1. Log in to Reddit with the account that should own the app. For app-only / installed grants this can be any account; for grant_type=password it must be the account whose credentials you're going to use.

  2. Scroll to the bottom of https://www.reddit.com/prefs/apps and click "create app" (or "create another app").

  3. Pick the app type:

    • script — when you plan to use grant_type=password or the default client_credentials. This is the right choice for personal/CLI tools and backend services where you own the account. Gets the "full" rate limit.

    • web app — for server-side web apps using the authorization-code flow (not covered by this provider; use it only with client_credentials here).

    • installed app — for mobile/desktop clients without a server-held secret. Use with grant_type=installed and supply a per-device device_id.

  4. Fill in name, description, and about url (anything reasonable). For redirect uri use http://localhost:8000 (not used by this provider but the form demands a value).

  5. Submit. Reddit shows you two strings:

    • The client_id: the short string shown right under the app name (looks like AbCdEfGhIjKlMn).

    • The secret: next to the label "secret". Copy it now — it won't be shown again in full.

    For installed apps there is no secret — pass client_secret => ''.

  6. Choose a real User-Agent. Reddit's API wiki explicitly asks for the form <platform>:<app-id>:<version> (by /u/<your-reddit-name>), e.g. my-search-bot/1.0 by /u/myaccount. Generic UAs (Python-requests, curl, LWP::UserAgent) get throttled into oblivion. Pass it via user_agent => ... on new.

  7. Respect the rate limits. Authenticated OAuth clients get ~100 QPM (queries per minute) per OAuth-ID. Script apps pin to the account, app-only / installed pin to the client_id.

After setup, the provider handles the token dance for you: it POSTs to https://www.reddit.com/api/v1/access_token, caches the bearer token until token_margin seconds before expiry, and attaches it to every search request against https://oauth.reddit.com.

SEE ALSO

Net::Async::WebSearch::Provider::Reddit, https://www.reddit.com/prefs/apps, https://github.com/reddit-archive/reddit/wiki/OAuth2, https://support.reddithelp.com/hc/en-us/articles/16160319875092-Reddit-Data-API-Wiki

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-net-async-websearch/issues.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <torsten@raudssus.de> https://raudss.us/

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.