NAME

InternetData::Oauth - sign a person in with OAuth

SYNOPSIS

my $client = InternetData->new;

my $device = $client->oauth->device_authorization('your-client-id',
    scope => 'account.read apikeys.read apikeys.reveal');
print "Open $device->{verification_uri} and enter $device->{user_code}\n";

my $token = $client->oauth->poll_device_token('your-client-id', $device);
my $keyed = InternetData->new(api_key => $token->{apikey});

DESCRIPTION

A program running on the person's own machine lets them sign in with a browser and pick one of their API keys, instead of asking them to paste it: the device flow. An app that can take a browser redirect signs them in there instead: the authorization code flow, with a PKCE pair made for that one sign-in. Reached through "oauth" in InternetData.

Every method takes a client ID, issued on request from support@internetdata.io; for the authorization code flow it can also be the https URL of a client metadata document the app serves. None of these requests carries the client's API key, and none needs one. Every method that sends a request takes a per-call timeout in seconds, bounding each request it sends, and has a _p twin returning a Mojo::Promise.

Answers are hash references keyed by the wire names. A member the server did not send has no key at all, and an empty scope is present.

A refusal dies with a InternetData::OauthError; anything else, a transport failure or a server error, with the ordinary InternetData::Error.

METHODS

metadata(%options)

The authorization server's discovery document: issuer, authorization_endpoint and token_endpoint, plus whichever of device_authorization_endpoint, revocation_endpoint, scopes_supported, response_types_supported, grant_types_supported, code_challenge_methods_supported, token_endpoint_auth_methods_supported, authorization_response_iss_parameter_supported, client_id_metadata_document_supported and service_documentation it carries.

device_authorization($client_id, %options)

Starts a device sign-in. scope is space-delimited and sent as given, and resource names the API the tokens are for; both are left out when not given. Returns device_code, user_code, verification_uri, expires_in and interval, and verification_uri_complete when the server sends it. Show the person user_code and verification_uri, then pass the hash to poll_device_token.

exchange_device_code($client_id, $device_code, %options)

Asks once whether the person approved. Until they do, it dies with an InternetData::OauthError coded authorization_pending. Never retried.

exchange_refresh_token($client_id, $refresh_token, %options)

Trades a refresh token for a new pair. The one presented is spent, so keep the refresh_token each call returns. A refresh names the key the person picked (apikey_id) but never reveals it again (apikey). Never retried.

Both exchanges return access_token, token_type and expires_in, and whichever of refresh_token, scope, apikey_id and apikey the server sent. apikey_id without apikey is normal: the key itself also needs a sign-in rather than a refresh, and a key whose secret can be shown again.

revoke($client_id, $token, %options)

Ends a token. A refresh token ends the whole sign-in and every token it issued, which is how a program signs the machine out; an access token ends only itself.

poll_device_token($client_id, $device, %options)

Waits for the person to approve, and returns the tokens. It waits interval seconds (5 when that is below 1) before EVERY request, the first included, and 5 more for the rest of the call each time the server answers slow_down, but never past expires_in: a wait that would end later ends then. It ends at the first answer that is neither: a denial dies with InternetData::OauthAccessDeniedError, a code that ran out with InternetData::OauthExpiredTokenError - as does outliving expires_in, counted from this call, with no status - and any other failure as it came. The timeout bounds each request, never the poll.

There is no cancellation handle: the blocking form returns only at one of those outcomes. The waits are event-loop timers, so poll_device_token_p shares a running Mojo::IOLoop with everything else on it.

authorization_url($client_id, $redirect_uri, $code_challenge, %options)

The URL to open in the person's browser for the authorization code flow, from a PKCE challenge. Makes no request. scope, state and resource are added when given and left out when empty; the redirect brings state back as it was sent, beside a code for exchange_authorization_code, or with an error. Every value is percent-encoded over UTF-8, leaving only A-Z a-z 0-9 - . _ ~ literal. An empty required value, or one with no UTF-8 form, croaks.

exchange_authorization_code($client_id, $code, $code_verifier, $redirect_uri, %options)

Trades the code the redirect brought back for tokens, answering as the two exchanges above do. $code_verifier is the PKCE verifier whose challenge went into the authorization URL, and $redirect_uri that URL's, exactly. Never retried: the server spends the code on first read, before it checks the verifier.

create_pkce

A fresh PKCE pair for one sign-in, as a hash reference: verifier, 32 bytes from OpenSSL's secure random source as 43 characters of unpadded base64url; challenge, its SHA-256 as unpadded base64url; and method, S256, the only one the server accepts. The challenge goes into the authorization URL, the verifier only to the exchange.

pkce_challenge($verifier)

The S256 challenge for a verifier of your own.