Skip to content

oauth plugin

Alexander Herling edited this page Oct 12, 2026 · 5 revisions

Writing an OAuth sign-in plugin

An OAuth plugin signs an account in by an authorization-code redirect with PKCE, by a device code, or both, and renews the token before it expires. It is the sibling of the sign-in plugin for providers whose tokens do not last.

Scaffold rdownloader plugin new --type oauth
plugin_type oauth
World oauth-plugin
Exports begin, poll, device-begin, device-poll, refresh
Template sdk/templates/oauth
Contract Interface oauth

What the host promises

  • You never see a credential. The tokens an exchange produces go back through store-oauth-token; later requests reach them only as {{secret:<reference>}}.
  • You do not choose where the person is sent. The address begin returns must lie on a domain the manifest declares; the host refuses anything else.
  • You remember nothing. What has to survive from begin to poll — the PKCE verifier — travels in flow-state, which the host stores verbatim and never shows.

Permissions

The scaffold asks for net_http with the provider's authorization and token endpoints; that list also bounds the sign-in page a person is sent to. credentials is granted by the type.

oauth_flows = ["redirect", "device"] at the top of manifest.toml — above the first [table] header, or TOML makes it a key of that table — names the ways in the plugin serves. The host calls only what it names; a manifest without it serves the redirect alone. [extension] claims names the provider slug it signs in.

Ask for exactly what you use: the install preview shows every grant, and a component importing an interface its manifest does not grant cannot be instantiated. Every capability is listed under Capabilities.

Rules worth knowing first

The PKCE verifier and the state are built from host.random-bytes — 32 bytes of operating system entropy each, drawn separately — and from nothing else. A value computed from the clock and the account id can be recomputed by anybody holding those inputs. An empty answer from host.random-bytes is a refusal: fail the sign-in on it.

Three rules the flow turns on:

  1. A provider that refuses ends the sign-in; one that cannot be reached must not.
  2. "Not confirmed yet" is waiting, neither of the above.
  3. Nothing a provider wrote is ever repeated verbatim.

Step by step

1. Scaffold it

plugin new reads its templates from sdk/templates, so run it from a checkout of the repository:

git clone https://github.com/degoya/rDownloader.git
cd rDownloader
rdownloader plugin new --type oauth --out ../myprovider
cd ../myprovider

Without an installation of rDownloader, the packaging tool rd-pack — cargo build --profile release-test -p rd-pack in the checkout — takes the same arguments: target/release-test/rd-pack plugin new --type oauth --out ../myprovider.

The directory name becomes the crate name and the plugin's slug (--name sets another). The scaffold gets its own plugin id, its own copy of the contract in wit/rdownloader.wit and its own Ed25519 key pair: plugin-signing.key, whose public half is already in manifest.toml. Keep that file out of version control.

2. Make it yours

  • src/pkce.rs writes PKCE, SHA-256 and base64url out — a guest has no WASI to borrow them from.
  • src/flow.rs reads the token answers.
  • src/guest.rs holds both entrances and refresh. Delete the entrance your provider does not offer, from oauth_flows and from src/guest.rs alike.
  • manifest.toml: oauth_flows, [capabilities.net_http] domains, [extension] claims.
  • locales/en.json: the English text of every failure code you report.

Change description and author under [metadata], and read the comment at the top of src/lib.rs: it states what the host promises this type.

3. Test it

cargo test

The unit tests need no WebAssembly toolchain: only src/guest.rs is wasm32-specific. Give every answer of your provider you read a test of its own.

4. Build the component

cargo build --release --target wasm32-unknown-unknown
wasm-tools component new target/wasm32-unknown-unknown/release/myprovider.wasm -o target/myprovider.wasm

This needs the wasm32-unknown-unknown target and wasm-tools 1.261.0, the version the bundled plugins are made with: cargo build makes a core module that carries the world src/guest.rs generates, and wasm-tools component new turns it into the component. The plugin is built without WASI: a component importing anything outside rdownloader:plugin is refused — see Building without WASI.

5. Package and sign it

rdownloader plugin package --manifest manifest.toml \
  --component target/myprovider.wasm \
  --locales locales --key plugin-signing.key --output myprovider.rdplug
rdownloader plugin conformance myprovider.rdplug --json

conformance answers whether this application would run the package; it has to be clean before you publish. For a key that outlives the scaffold, rdownloader plugin keygen --role plugin --output ./keys writes a pair: the Base64 in the .pub file goes into public_key, and key_id gets a name of your own. In CI, RDOWNLOADER_PLUGIN_SIGNING_KEY holding the private key's PEM text replaces --key. Details: Packaging, signing and distribution.

6. Install and trust it

For a first try without signing, package with --development instead of --key and start rdownloader serve --plugin-development-mode, the only mode that accepts an unsigned package.

Install a signed package under Settings → Plugins → Add as an upload, or with rdownloader plugin install myprovider.rdplug --root data/plugins. Review before installing shows every permission and the signing key; a key this installation has not seen asks to be trusted — compare its fingerprint. The message after installing says whether the plugin runs at once or after the next restart; see Installing and trust.

The account form of the provider named in claims, under Settings → Accounts, offers the sign-in; the renewal runs by itself before the token expires.

7. Publish it

Publish the .rdplug together with your key's fingerprint, or — so that people get your new versions in the Catalogue — in a plugin repository of your own: a signed index built with rdownloader plugin index build and a repository-role key. A repository only delivers: every package in it still needs your plugin key, approved by the person installing it. See A repository index and Running a plugin repository.

The SDK's reusable CI workflow, sdk/ci/plugin.yml, builds, checks the imports, packages and runs conformance on every push and pull request; it signs only on a push to the default branch or a tag, so a pull request never sees the key.

Read next

Clone this wiki locally