CLI and lib to manage OAuth 2.0 tokens, written in Rust.
This repository ships three layers:
- Low-level I/O-free coroutines: no_std-friendly state machines that emit read/write requests for any runtime.
- Mid-level blocking client wrapping the coroutines around any
Read + Write + Sendstream, or (with a TLS feature on) building the TLS stream itself via pimalaya-stream. - High-level CLI consuming the std client, configured through TOML.
- OAuth 2.0 Authorization Code Grant rfc6749 #4.1 and refresh rfc6749 #6
- PKCE rfc7636
- TLS support:
- Rustls with ring crypto (
rustls-ringfeature, default) - Rustls with aws crypto (requires
rustls-awsfeature) - Native TLS (requires
native-tlsfeature)
- Rustls with ring crypto (
- Fake HTTP redirection server during the interactive flow
- Shell command storages for reading and writing access tokens
- Shell command hooks on success and error of token issuance / refresh
- System notification hooks (requires
notifyfeature) - JSON output via
--json
Tip
Ortie is written in Rust and uses cargo features to gate optional functionality. The default feature set is declared in Cargo.toml.
Ortie can be installed with the installer:
As root:
curl -sSL https://raw.githubusercontent.com/pimalaya/ortie/master/install.sh | sudo shAs a regular user:
curl -sSL https://raw.githubusercontent.com/pimalaya/ortie/master/install.sh | PREFIX=~/.local shThese commands install the latest binary from the GitHub releases section.
For a more up-to-date version than the latest release, check out the releases GitHub workflow and look for the Artifacts section. These pre-built binaries are built from the master branch.
Note
Such binaries are built with the default cargo features. If you need specific features, please use another installation method.
cargo install --locked ortieFor the git tip:
cargo install --locked --git https://github.com/pimalaya/ortie.gitIf you have the Flakes feature enabled:
nix profile install github:pimalaya/ortieOr run without installing:
nix run github:pimalaya/ortiegit clone https://github.com/pimalaya/ortie
cd ortie
nix runOrtie does not yet ship a wizard: copy config.sample.toml into one of the canonical paths below and edit it by hand.
A configuration is loaded from the first valid path among:
$XDG_CONFIG_HOME/ortie/config.toml$HOME/.config/ortie/config.toml$HOME/.ortierc
Override the path with -c <PATH> or ORTIE_CONFIG=<PATH>; multiple paths can be passed at once, separated by :. The first one is the base and the rest are deep-merged on top.
You will also need a registered OAuth 2.0 application: either use a public application (Thunderbird credentials cover most consumer providers) or register your own. The first option is simpler.
See public Thunderbird application credentials for various providers at github.com/mozilla.
endpoints.authorization = "https://accounts.google.com/o/oauth2/auth?access_type=offline"
endpoints.token = "https://www.googleapis.com/oauth2/v3/token"
scopes = ["https://www.googleapis.com/auth/carddav", "https://mail.google.com"]Using the public Thunderbird application:
client-id = "406964657835-aq8lmia8j95dhl1a2bvharmfk3t1hgqj.apps.googleusercontent.com"
client-secret.raw = "kSmqreRr0qwBWJgbf5Y-PjSU"
endpoints.redirection = "http://localhost"Using your own application:
client-id = "<your-client-id>"
client-secret.raw = "<your-client-secret>"endpoints.authorization = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
endpoints.token = "https://login.microsoftonline.com/common/oauth2/v2.0/token"Using the public Thunderbird application:
client-id = "9e5f94bc-e8a4-4e73-b8be-63364c29d753"
endpoints.redirection = "https://localhost"Using your own application:
client-id = "<your-client-id>"
client-secret.raw = "<your-client-secret>"The low-level coroutines live under ortie::authorization_code_grant, ortie::issue_access_token and ortie::refresh_access_token; they emit WantsRead / WantsWrite(bytes) events the caller pumps against any transport. The mid-level ortie::client::OauthClientStd inlines the loop against a single boxed stream, with two construction paths:
use ortie::{
client::OauthClientStd,
refresh_access_token::RefreshAccessTokenParams,
};
// Light: caller owns the connection (TCP, mock, plaintext, …).
let mut client = OauthClientStd::new(my_stream, token_endpoint, &client_id);
// Full (requires `rustls-ring` / `rustls-aws` / `native-tls`):
// opens the TLS stream itself via pimalaya-stream.
let mut client = OauthClientStd::connect(token_endpoint, &tls, &client_id)?;
let res = client.refresh_access_token(RefreshAccessTokenParams {
client_id: client_id.into(),
refresh_token,
scopes,
})?;A complete example using the authorization code grant flow lives in examples/authorization_code_grant.rs.
$ ortie auth get
Created authorization request with:
- state: RWdzST0ybUIzT1wtMSF9OCMmJHJUVmJrUmhhU0haLz4
- pkce: oJ-rEXNu9YzqpCWVIPOwD5KvMhLAT73dstk0jye8nZ6
Sending authorization request to your browser:
https://login.example.com/oauth/authorize?…
Wait for redirection…Follow the browser flow, then on success the terminal shows:
Access token successfully issued (expires in 1h)If the redirection server cannot start (port permission denied, etc.), copy the URL you are redirected to and complete the flow manually:
ortie auth resume \
--state RWdzST0ybUIzT1wtMSF9OCMmJHJUVmJrUmhhU0haLz4 \
--pkce oJ-rEXNu9YzqpCWVIPOwD5KvMhLAT73dstk0jye8nZ6 \
https://localhost/?code=M.C521_BAY.2.U&state=RWdzST0ybUIzT1wtMSF9OCMmJHJUVmJrUmhhU0haLz4$ ortie token refresh
Access token successfully refreshed (expires in 1h)$ ortie token show
EwA4BOl3BAAUcDnR9grBJokeAHaUV8R3+rVHX+IAAQfw9oZLztQS8bo8NvyWmbs…The --auto-refresh flag (or the auto-refresh = true config option) automatically refreshes expired tokens.
Inspect token metadata:
$ ortie token inspect
Token type: bearer
Issued: 22h 51m 1s ago
Expires in: 52m 38s
With refresh token: true
With scope: https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.SendThe --log-level <LEVEL> flag controls log verbosity (off, error, warn, info, debug, trace). When omitted, RUST_LOG is consulted; it supports per-target filters (see the env_logger docs). RUST_BACKTRACE=1 enables the full error backtrace.
Logs go to stderr by default; redirect them with --log-file <PATH> or shell redirection:
ortie token show --log-level debug --log-file /tmp/ortie.log
ortie token show --log-level trace 2>/tmp/ortie.log- pizauth: daemon-oriented alternative
- oama: Haskell alternative
- mutt_oauth2.py: Python script alternative
This project is developed with AI assistance. This section documents how, so users and downstream packagers can make informed decisions.
-
Tools: Claude Code (Anthropic), Opus 4.7, invoked locally with a persistent project-scoped memory and a small set of repo-specific rules.
-
Used for: Refactors, mechanical multi-file edits, boilerplate (feature gates, error enums, derive macros, trait impls), test scaffolding, doc polish, exploratory design conversations.
-
Not used for: Engineering, critical code, git manipulation (commit, merge, rebase…), real-world tests.
-
Verification: Every AI-assisted change is read, compiled, tested, and formatted before commit (
nix develop --command cargo check / cargo test / cargo fmt). Behavioural correctness is verified against the relevant RFC or upstream spec, not assumed from the model output. Tests are never adjusted to fit AI-generated code; the code is adjusted to fit correct behaviour. -
Limitations: AI models occasionally produce code that compiles and passes tests but is subtly wrong: off-by-one errors, missed edge cases, plausible but nonexistent APIs, stale RFC references. The verification workflow catches most of this; it does not catch all of it. Bug reports are welcome and taken seriously.
-
Last reviewed: 30/05/2026
- Chat on Matrix
- News on Mastodon or RSS
- Mail at pimalaya.org@posteo.net
Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:
- 2022 → 2023: NGI Assure
- 2023 → 2024: NGI Zero Entrust
- 2024 → 2026: NGI Zero Core
- 2027 in preparation…
If you appreciate the project, feel free to donate using one of the following providers:
