The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Package {tinyoauth}


Type: Package
Title: Minimal OAuth 2.0 Client
Version: 0.1.1
Date: 2026-06-22
Description: A dependency-light OAuth 2.0 https://www.rfc-editor.org/rfc/rfc6749 client supporting the client-credentials and authorization-code grants with token refresh. Built on 'curl' and 'jsonlite', with base R's socket server for the redirect listener, avoiding heavier HTTP stacks.
License: MIT + file LICENSE
Encoding: UTF-8
Depends: R (≥ 4.0)
Imports: curl, digest, jsonlite
Suggests: tinytest
URL: https://github.com/cornball-ai/tinyoauth
BugReports: https://github.com/cornball-ai/tinyoauth/issues
NeedsCompilation: no
Packaged: 2026-06-23 15:01:08 UTC; troy
Author: Troy Hernandez ORCID iD [aut, cre], cornball.ai [cph]
Maintainer: Troy Hernandez <troy@cornball.ai>
Repository: CRAN
Date/Publication: 2026-06-24 07:40:07 UTC

Minimal OAuth 2.0 Client

Description

A dependency-light OAuth 2.0 <https://www.rfc-editor.org/rfc/rfc6749> client supporting the client-credentials and authorization-code grants with token refresh. Built on 'curl' and 'jsonlite', with base R's socket server for the redirect listener, avoiding heavier HTTP stacks.

Package Content

Index of help topics:

anthropic_claude_client
                        OAuth client for the Anthropic Claude (Claude
                        Code) login flow
oauth_authorize_url     Build an authorization URL
oauth_bearer            Authorization header value for a token
oauth_cache_path        Default on-disk cache path for a client's token
oauth_client            Define an OAuth 2.0 client
oauth_exchange_code     Exchange an authorization code for a token
oauth_expired           Is a token expired?
oauth_import_httr       Import an httr '.httr-oauth' cache into
                        tinyoauth
oauth_jwt_payload       Decode a JWT payload
oauth_refresh           Refresh an access token
oauth_request           Make an authenticated request
oauth_token             Get a valid token, using the cache and
                        refreshing as needed
oauth_token_anthropic   Get a valid Anthropic Claude token, using the
                        cache and refreshing as needed
oauth_token_authcode    Run the authorization-code flow end to end
oauth_token_client      Fetch a token via the client-credentials grant
oauth_token_openai_codex
                        Get a valid OpenAI Codex token, using the cache
                        and refreshing as needed
openai_codex_account_id
                        Extract the ChatGPT account id from a Codex
                        token
openai_codex_client     OAuth client for the OpenAI Codex (ChatGPT)
                        device-login flow

Maintainer

Troy Hernandez <troy@cornball.ai>

Author(s)

Troy Hernandez [aut, cre] (ORCID: <https://orcid.org/0009-0005-4248-604X>), cornball.ai [cph]


Build the Claude authorization URL (with PKCE)

Description

The code=true parameter selects the manual flow, where the callback page displays the authorization code (as <code>#<state>) for pasting.

Usage

.anthropic_authorize_url(client, pkce, state)

Exchange a Claude authorization code (with its PKCE verifier) for a token

Description

Exchange a Claude authorization code (with its PKCE verifier) for a token

Usage

.anthropic_exchange(client, code, state, verifier)

Run the Claude login flow end to end (display URL, paste code, exchange)

Description

Run the Claude login flow end to end (display URL, paste code, exchange)

Usage

.anthropic_login(client, open_url = interactive())

A random base64url token of nbytes entropy

Description

32 bytes encodes to a 43-char base64url string, matching the format the Claude Code client uses for both the PKCE verifier and the OAuth state.

Usage

.anthropic_nonce(nbytes = 32L)

Parse the value pasted back from Claude's callback page

Description

Claude's callback displays the code as <code>#<state>. Also accepts the full redirected URL (...callback?code=...&state=...) or a bare code.

Usage

.anthropic_parse_code(x)

Generate a PKCE verifier and its S256 challenge

Description

The verifier is a 43-char base64url token (32 bytes of entropy, matching the Claude Code client); the challenge is the base64url-encoded SHA-256 of the verifier's ASCII bytes.

Usage

.anthropic_pkce()

POST a JSON body to an Anthropic OAuth endpoint and parse into a token

Description

Claude's token endpoint takes JSON (the generic [.token_request] form-encodes, which Anthropic rejects), so this route needs its own POST. Reuses [.as_token].

Usage

.anthropic_post_json(url, body)

Refresh a Claude token (JSON refresh-token grant)

Description

Refresh a Claude token (JSON refresh-token grant)

Usage

.anthropic_refresh(client, token)

Build a tinyoauth_token from a parsed token response

Description

Build a tinyoauth_token from a parsed token response

Usage

.as_token(body)

base64url-encode a raw vector (no padding)

Description

base64url-encode a raw vector (no padding)

Usage

.b64url(raw)

HTTP Basic authorization header value for client credentials

Description

HTTP Basic authorization header value for client credentials

Usage

.basic_auth(id, secret)

Poll the device-token endpoint until the user authorizes (or we time out)

Description

Poll the device-token endpoint until the user authorizes (or we time out)

Usage

.codex_device_poll(client, device, timeout = 600, sleep = Sys.sleep,
                   post = .codex_post_json)

Arguments

sleep

Sleep function, injectable for testing.

post

JSON-POST function, injectable for testing.


Start the device-authorization flow: get a user code to display

Description

Start the device-authorization flow: get a user code to display

Usage

.codex_device_start(client, post = .codex_post_json)

Arguments

post

JSON-POST function, injectable for testing.


Exchange the device authorization code (with its PKCE verifier) for a token

Description

Exchange the device authorization code (with its PKCE verifier) for a token

Usage

.codex_exchange(client, code, verifier)

Attach the ChatGPT account id (from the access-token JWT) to a token

Description

Attach the ChatGPT account id (from the access-token JWT) to a token

Usage

.codex_finalize(token)

Run the device-login flow end to end (display code, wait, exchange)

Description

Run the device-login flow end to end (display code, wait, exchange)

Usage

.codex_login(client, open_url = interactive(), timeout = 600)

Classify a device-token poll response

Description

Returns one of "ok" (authorization granted), "pending" (keep waiting), "slow_down" (back off), or "error" (give up).

Usage

.codex_poll_classify(status, body)

POST a JSON body and parse the JSON response

Description

The device endpoints take JSON (the token endpoint takes form-encoding, which is what [.token_request] handles).

Usage

.codex_post_json(url, body)

Drop NULL list elements

Description

Drop NULL list elements

Usage

.drop_null(x)

Fetch with a few retries on transport errors / 5xx

Description

Fetch with a few retries on transport errors / 5xx

Usage

.fetch_retry(url, handle, times = 3L)

Form-encode a named list as application/x-www-form-urlencoded, dropping NULLs

Description

Form-encode a named list as application/x-www-form-urlencoded, dropping NULLs

Usage

.form_encode(fields)

Catch a single OAuth redirect on a loopback port

Description

Opens a one-shot serverSocket() listener, accepts the browser redirect, replies with a small page, and returns the parsed query.

Usage

.listen_for_redirect(port = 1410L, timeout = 120)

Arguments

port

Loopback port to listen on (must match the redirect URI).

timeout

Seconds to wait for the redirect.

Value

Named list of query parameters from the redirect.


Is this a session where a loopback OAuth listener cannot catch the redirect?

Description

TRUE for SSH sessions, RStudio Server, and headless unix (no X or Wayland display): in all of these the browser runs on a different machine, so the redirect to a 127.0.0.1 listener never arrives and the listener would hang. [oauth_token_authcode] uses this to default to the manual paste flow. macOS desktops have no DISPLAY but a working browser, so they are not treated as headless.

Usage

.oauth_no_loopback()

Parse a pasted redirect URL (or bare code) into query parameters

Description

Accepts the full http://127.0.0.1:.../?code=...&state=... address the browser landed on, a bare code=...&state=... query string, or just the code value on its own. Used by the manual = TRUE (no-listener) path.

Usage

.parse_redirect_input(x)

Parse the query parameters from an HTTP request line

Description

Parse the query parameters from an HTTP request line

Usage

.parse_request_query(req_line)

POST a grant to the token endpoint and parse the result

Description

POST a grant to the token endpoint and parse the result

Usage

.token_request(client, fields)

OAuth client for the Anthropic Claude (Claude Code) login flow

Description

A preconfigured [oauth_client] for Claude-subscription-backed access, carrying Anthropic's authorize and token endpoints plus the Claude Code scope string. The client id is Anthropic's public Claude Code identifier, not a secret.

Usage

anthropic_claude_client()

Value

A tinyoauth_client with an extra scope field.

Examples

anthropic_claude_client()

Build an authorization URL

Description

Build an authorization URL

Usage

oauth_authorize_url(client, scope = NULL, state = NULL)

Arguments

client

A [oauth_client] with an auth_url.

scope

Optional space-delimited scope string.

state

Optional opaque state for CSRF protection.

Value

The authorization URL to open in a browser.

Examples

oauth_authorize_url(
  oauth_client("id", token_url = "https://x/token",
               auth_url = "https://x/authorize"),
  scope = "user-read-email")

Authorization header value for a token

Description

Authorization header value for a token

Usage

oauth_bearer(token)

Arguments

token

A tinyoauth_token, a (legacy) httr Token2.0, or a raw access-token string.

Value

A string like "Bearer abc123" for use as an HTTP Authorization header.

Examples

## Not run: 
h <- curl::new_handle()
curl::handle_setheaders(h, Authorization = oauth_bearer(tok))

## End(Not run)

Default on-disk cache path for a client's token

Description

Default on-disk cache path for a client's token

Usage

oauth_cache_path(client)

Arguments

client

A [oauth_client].

Value

Path to the token cache file under tools::R_user_dir.


Define an OAuth 2.0 client

Description

Define an OAuth 2.0 client

Usage

oauth_client(id, secret = NULL, token_url, auth_url = NULL,
             redirect_uri = "http://127.0.0.1:1410/")

Arguments

id

Client (application) id.

secret

Client secret, or NULL for public clients.

token_url

The provider's token endpoint.

auth_url

The provider's authorization endpoint (needed for the authorization-code grant; omit for client-credentials only).

redirect_uri

Redirect URI registered with the provider. Use a loopback IP literal over http (127.0.0.1); many providers reject localhost.

Value

A tinyoauth_client object.

Examples

spotify <- oauth_client(
  id = "your_id", secret = "your_secret",
  token_url = "https://accounts.spotify.com/api/token",
  auth_url  = "https://accounts.spotify.com/authorize")

Exchange an authorization code for a token

Description

Exchange an authorization code for a token

Usage

oauth_exchange_code(client, code)

Arguments

client

A [oauth_client].

code

The authorization code from the redirect.

Value

A tinyoauth_token.


Is a token expired?

Description

Is a token expired?

Usage

oauth_expired(token, leeway = 60)

Arguments

token

A tinyoauth_token.

leeway

Seconds of slack before the hard expiry (default 60).

Value

TRUE if expired (or within leeway of it); FALSE when there is no expiry recorded.


Import an httr '.httr-oauth' cache into tinyoauth

Description

Reads a token cached by httr's oauth2.0_token() and returns a tinyoauth client and token built from it – the app credentials, endpoints, and (crucially) the refresh token. This lets a package migrating off httr reuse an existing authorization instead of forcing users to log in again.

Usage

oauth_import_httr(path = ".httr-oauth", which = 1L)

Arguments

path

Path to the httr cache (default ".httr-oauth").

which

Which cached token to import when the file holds several (1-based; default 1).

Details

The imported access token is marked expired, since httr's cached access token is usually stale: the durable credential is the refresh token. Pass the result to [oauth_refresh] or [oauth_token] to mint a fresh access token.

Value

A list with client (a [oauth_client]) and token (a tinyoauth_token).

Examples

## Not run: 
imported <- oauth_import_httr("~/project/.httr-oauth")
token <- oauth_refresh(imported$client, imported$token)

## End(Not run)

Decode a JWT payload

Description

Base64url-decodes the payload (middle) segment of a JSON Web Token and parses it as JSON. Does not verify the signature; use only on tokens you already trust (e.g. one the provider just issued you).

Usage

oauth_jwt_payload(x)

Arguments

x

A JWT string, or a tinyoauth_token (its access_token is used).

Value

The decoded payload as a named list, or NULL if x has no usable JWT.

Examples

# A toy token: header.payload.signature, payload = {"sub":"abc"}
payload <- jsonlite::base64_enc(charToRaw('{"sub":"abc"}'))
jwt <- paste("x", gsub("=", "", payload), "y", sep = ".")
oauth_jwt_payload(jwt)$sub

Refresh an access token

Description

Refresh an access token

Usage

oauth_refresh(client, token)

Arguments

client

A [oauth_client].

token

A tinyoauth_token carrying a refresh token.

Value

A refreshed tinyoauth_token. Providers that omit a new refresh token on refresh keep the existing one.

Examples

## Not run: 
tok <- oauth_refresh(spotify, tok)

## End(Not run)

Make an authenticated request

Description

Sends an HTTP request with the token as a Bearer header, retrying transient failures, and parses a JSON response. A convenience over building a curl handle by hand; for anything exotic, use [oauth_bearer] with curl directly.

Usage

oauth_request(token, url, method = "GET", query = NULL, body = NULL,
              headers = NULL, flatten = FALSE, retries = 3L)

Arguments

token

A tinyoauth_token, a (legacy) httr token, or a raw access-token string.

url

Endpoint URL.

method

HTTP method (default "GET").

query

Optional named list of query parameters.

body

Optional R object sent as a JSON body.

headers

Optional named character vector of extra headers.

flatten

Passed to jsonlite::fromJSON (default FALSE).

retries

Attempts on transport errors / HTTP 5xx (default 3).

Value

Parsed JSON, or invisibly NULL for an empty response body. Non-2xx responses raise an error carrying the status and body.

Examples

## Not run: 
oauth_request(tok, "https://api.spotify.com/v1/me")

## End(Not run)

Get a valid token, using the cache and refreshing as needed

Description

Returns a cached token if still valid; refreshes it if expired and a refresh token is available; otherwise runs the authorization-code flow. The result is written back to cache.

Usage

oauth_token(client, scope = NULL, cache = oauth_cache_path(client), ...)

Arguments

client

A [oauth_client].

scope

Optional space-delimited scope string (for first authorization).

cache

Cache file path, or NULL to disable caching. Defaults to [oauth_cache_path].

...

Passed to [oauth_token_authcode] (e.g. port, open_browser).

Value

A valid tinyoauth_token.

Examples

## Not run: 
tok <- oauth_token(spotify, scope = "user-read-email")

## End(Not run)

Get a valid Anthropic Claude token, using the cache and refreshing as needed

Description

The Claude analogue of [oauth_token]: returns a cached token if still valid, refreshes it if expired and a refresh token is available, otherwise runs the manual-paste login flow. The token is written back to cache.

Usage

oauth_token_anthropic(cache = oauth_cache_path(anthropic_claude_client()),
                      open_url = interactive(), login = TRUE)

Arguments

cache

Cache file path, or NULL to disable caching. Defaults to [oauth_cache_path] for the Claude client.

open_url

Open the authorization URL automatically (default: interactive sessions only).

login

Run the login flow when no usable cached/refreshable token exists (default TRUE). Pass FALSE to get the cached (and refreshed-if-needed) token or NULL, without ever prompting – useful inside a request path where an interactive login would be wrong.

Details

These are subscription credentials minted for Claude Code; using them is subject to Anthropic's terms for that product.

Value

A tinyoauth_token with access_token, refresh_token, and expires_at; or NULL when login is FALSE and no usable token is cached.

Examples

## Not run: 
tok <- oauth_token_anthropic()
curl::handle_setheaders(curl::new_handle(),
                        Authorization = oauth_bearer(tok),
                        "anthropic-beta" = "oauth-2025-04-20")

## End(Not run)

Run the authorization-code flow end to end

Description

Prints (and optionally opens) the authorization URL, then obtains the redirect either by catching it on a loopback listener (default) or, with manual = TRUE, by having you paste the redirected URL back. After verifying state, it exchanges the code.

Usage

oauth_token_authcode(client, scope = NULL, port = 1410L,
                     open_browser = interactive(), timeout = 120, manual = NA)

Arguments

client

A [oauth_client] with an auth_url.

scope

Optional space-delimited scope string.

port

Loopback port for the listener; must match the port in client$redirect_uri (default 1410).

open_browser

Open the URL automatically (default: interactive only).

timeout

Seconds to wait for the redirect.

manual

Skip the loopback listener and read the redirected address (or bare code) from the console instead. The default (NA) auto-detects: it switches to manual on a remote/headless session (SSH, RStudio Server, or unix with no display), where the browser runs elsewhere and the redirect can never reach a local listener (so the listener would just hang). Pass TRUE/FALSE to force it. In manual mode the browser shows a "can't reach 127.0.0.1" page after you approve – that is expected; copy its address bar and paste it.

Value

A tinyoauth_token (with a refresh token, when the provider issues one).

Examples

## Not run: 
tok <- oauth_token_authcode(spotify, scope = "user-read-email")
tok <- oauth_token_authcode(google, manual = TRUE)  # force manual paste

## End(Not run)

Fetch a token via the client-credentials grant

Description

App-only access (no user context).

Usage

oauth_token_client(client)

Arguments

client

A [oauth_client].

Value

A tinyoauth_token.

Examples

## Not run: 
tok <- oauth_token_client(spotify)

## End(Not run)

Get a valid OpenAI Codex token, using the cache and refreshing as needed

Description

The Codex analogue of [oauth_token]: returns a cached token if still valid, refreshes it if expired and a refresh token is available, otherwise runs the device-login flow. The token carries an extra account_id field (the ChatGPT account id) and is written back to cache.

Usage

oauth_token_openai_codex(cache = oauth_cache_path(openai_codex_client()),
                         open_url = interactive(), timeout = 600, login = TRUE)

Arguments

cache

Cache file path, or NULL to disable caching. Defaults to [oauth_cache_path] for the Codex client.

open_url

Open the verification URL automatically (default: interactive sessions only).

timeout

Seconds to wait for device authorization (default 600).

login

Run the device-login flow when no usable cached/refreshable token exists (default TRUE). Pass FALSE to get the cached (and refreshed-if-needed) token or NULL, without ever prompting – useful inside a request path where an interactive login would be wrong.

Value

A tinyoauth_token with access_token, refresh_token, expires_at, and account_id; or NULL when login is FALSE and no usable token is cached.

Examples

## Not run: 
tok <- oauth_token_openai_codex()
curl::handle_setheaders(curl::new_handle(),
                        Authorization = oauth_bearer(tok),
                        "chatgpt-account-id" = tok$account_id)

## End(Not run)

Extract the ChatGPT account id from a Codex token

Description

Reads the chatgpt_account_id claim that OpenAI nests under https://api.openai.com/auth in the access-token JWT.

Usage

openai_codex_account_id(token)

Arguments

token

A tinyoauth_token (or raw access-token string).

Value

The account id string, or NULL if absent.


OAuth client for the OpenAI Codex (ChatGPT) device-login flow

Description

A preconfigured [oauth_client] for ChatGPT-subscription-backed Codex access, carrying OpenAI's device-authorization endpoints alongside the standard token endpoint. The client id is OpenAI's public native-app identifier, not a secret.

Usage

openai_codex_client()

Value

A tinyoauth_client with extra device_usercode_url, device_token_url, and verification_uri fields.

Examples

openai_codex_client()

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.