Misc

OAuth via GPALRestAPI - Capturing Google Authorization Codes

How the Handshake Works

When a workflow needs a Google authorization code and no refresh token is stored, GPAL.Credentials opens the auth URL in a browser, then polls /get-access-token on the local listener for up to 60 seconds waiting for the redirect to arrive. The code is cleared immediately on retrieval -- it is single use. If the 60-second window passes with nothing returned, GPAL asks for the code at the console; a forms program has no console, so GPAL publishes the auth URL as an error instead, and the code can be passed with WithAuthCode.

// GPAL.Credentials drives the full flow.

// No manual REST calls needed.

//

// 1. GPAL opens the Google auth URL in a browser.

// 2. User approves. Google redirects to:

// http://localhost:3000/access-token

// 3. GPAL polls /get-access-token for up to

// 60 seconds and collects the code.

// 4. If 60 seconds pass with no code, GPAL

// asks for it at the console, or, in a

// forms program, publishes the auth URL as

// an error; pass the code with WithAuthCode.

//

// The code is single-use -- cleared on retrieval.

// GPALRestAPI clears it after 5 minutes

// if it is never retrieved at all.

NOTE

In Google Cloud Console under your OAuth client, add http://localhost:3000/access-token to Authorized Redirect URIs. A web application client matches the redirect exactly, port included, which is why GPAL never moves the listener to a different port. Use a different port by all means, but change it in both places: GoogleRestRedirectUri and the console.

Where the Token Lands

The provider only redirects to the URI registered with it, so the address that catches the token is not a choice GPAL makes: it is the authority of GoogleRestRedirectUri, http://localhost:3000/ by default, read from credentialsConfig.json beside the exe.

Before opening the auth URL, GPAL tries to bind that address with its own minimal listener, which serves the same access-token and get-access-token endpoints on a background thread and shuts down once the code is collected.

If the bind fails then something already owns the port, and GPAL asks it whether it is GPALRestAPI by calling its status endpoint, waiting one second at most. If it answers, it catches the redirect as normal. If it does not, GPAL reports an error rather than listening somewhere else, because a redirect the provider never agreed to would not arrive anyway.

Whichever caught it is readable afterwards as GPAL.OAuthTokenUrl.

// GPAL checks whether something is already

// listening on the redirect base URL.

//

// If GPALRestAPI IS running:

// it owns the port -- redirect lands there.

//

// If GPALRestAPI is NOT running:

// GPAL binds the same base URL itself via

// LocalAuthCallbackServer.StartIfNeeded().

// Same /access-token and /get-access-token

// endpoints, same behavior.

// Listener is stopped after the code

// is retrieved.

NOTE

GPALRestAPI scans upward from 3000, so the first one on a machine takes it, which is why the usual case is that the GPALRestAPI your own browser launched is already sitting on the registered address. A second instance pushed to another port does not catch redirects, and cannot: there is one registered address, so two workflows doing OAuth at the same moment on one machine will collide.

GPAL Exchanges the Code

You do not post the code anywhere yourself. GPAL exchanges it at the token endpoint and keeps the access token and the refresh token on that Credentials object, refreshing the access token as it expires for the rest of the run. GPAL does not hand the new refresh token back or store it, so the next run starts at the consent page again. To run with no one at the keyboard, mint a refresh token once (the OAuth Playground does it) and supply it with WithRefreshToken; every run then exchanges it for a fresh access token with no browser.

ICredentials google = GPAL.Credentials

.WithService(CredentialServiceType.Google)

.WithClientId(clientId)

.WithClientSecret(clientSecret)

.WithScope(OAuthScope.Google_Drive)

.WithRefreshToken(refreshToken) // minted once, kept by you

.ToGPALObject();


// no browser, no consent page

google.FetchAccessToken(out string token);

NOTE

Without WithRefreshToken, every run begins at the consent page. With one, the workflow is fully automated with no user interaction required.

💬 Ask GPAL