We have an a WPF app that allows the user to import their Google contacts using the PeopleAPI. We're modifying it because Google recently changed their auth process. See here
The idea is to call the Google Auth page and use an HTTPListener to listen on a port for the redirect URI. Google has a WPF example here.
First I went into the Google Control panel and changed the RedirectURI from the page we were using to https://127.0.0.1.
Next I set up the HTTPListener to handle the callback. The problem is that by using a random port number, how can you specify a callback URI in the control panel?
I've tried multiple things. Here's my code. In this interation, I set the callback URI in the Google Control Panel to 127.0.0.1 and pass it as the callback in the request. But the listener is set to use a random port, so the two redirect URI's arnt really the same:
public async Task TryAuthorizeAsync()
{
// Generates state and PKCE values.
string state = RandomDataBase64url(32);
string codeVerifier = RandomDataBase64url(32);
string codeChallenge = Base64urlencodeNoPadding(SHA256(codeVerifier));
// Creates an HttpListener to listen for requests
string localHost = $"https://{IPAddress.Loopback}";
string listenerURI = $"{localHost}:{GetRandomUnusedPort()}/"; // Includes a random port #
var listener = new HttpListener();
RaiseStatusChanged($"listening on {listenerURI}");
listener.Prefixes.Add(listenerURI);
listener.Start();
// Create the authorization request passing <a href="https://127.0.0.1">https://127.0.0.1</a> as the redirect.
var authorizationRequest = $"{AuthorizationEndpoint}?response_type=code&" +
$"scope=openid%20profile&" +
$"redirect_uri={Uri.EscapeDataString(localHost)}&" +
$"client_id={ClientID}&" +
$"state={state}&" +
$"code_challenge={codeChallenge}&" +
$"code_challenge_method={CODE_CHALLENEGE_METHOD}";
// Opens request in the default browser
Process.Start(authorizationRequest);
bool success = false;
// Wait for the auth authorization response.
await Task.Run(() =>
{
// Begin waiting for context. Call the ListenerCallback when context is recieved
IAsyncResult context2 = listener.BeginGetContext(new AsyncCallback(result => ListenerCallback(result, state, codeVerifier, listenerURI)), listener);
if (HANDLE_TIMEOUT)
{
success = context2.AsyncWaitHandle.WaitOne(RESPONSE_TIMEOUT, true);
}
});
// If here and both handling the timeout and the auth was not successfull, then the user
// didn't do anything in the browser, so the auth process timed out. If the user did
// anything in the browser, the ListenerCallback method handles it.
if (HANDLE_TIMEOUT && !success)
{
RaiseAuthorizationTimedOut();
}
}
When I run this, the user's Google auth page opens in the browser and when the user selects their account and authorizes, but the ListenerCallback never fires.
What I do see is the page directs to
This site can't be reached 127.0.0.1 refused to connect
Agian, the problem seems to be is that the auth needs the redirect port number. But we can't use a dedicated port number in the Google API Console, so how then can I redirect to the listener method?
Strange: the
HttpListeneris listening on a random port, as you coded here:But the redirect URI that you are passing in the authorization request is just the localhost address without any port:
You should make sure that the redirect URI specified in the Google API Console, the redirect URI passed in the authorization request, and the URI your
HttpListeneris listening on are all the same.Note: The same consistency should apply to the scheme of the redirect URI is consistent. If you are using
httpsin the Google API Console, then theHttpListenermust also be configured to usehttps, and vice versa. If you are usinghttps, you will also need to handle the SSL certificate. For local testing, it might be easier to usehttpinstead.Since
googlesamples/oauth-apps-for-windowsOAuthDesktopApp/OAuthDesktopApp/MainWindow.xaml.cs is using a random port, you should dynamically obtain the port number assigned to theHttpListenerand update the redirect URI accordingly.That Google's sample app
OAuthDesktopApp/OAuthDesktopApp/MainWindow.xaml.cslikely uses a different approach called "out-of-band" (OOB) URI. This is a special redirect URI that tells the Google authorization server to return the authorization code in the title bar of the page, rather than sending it to a web server. Desktop apps can use this approach to obtain the authorization code without having to listen on a specific port.The idea is, in the Google API Console, to set the redirect URI for your OAuth 2.0 client ID to
urn:ietf:wg:oauth:2.0:oob.Then modify your authorization request to use the OOB URI as the redirect URI:
However, since Feb. 2022, such a legacy flow has been deprecated.
That is where you realize the random port, in that Google example, is 7 years old. And references a 2010 Stack Overflow question.
In other words, you no longer can use that example as-is.
You would need to migrate to another workflow, like OAuth 2.0 for TV and Limited-Input Device Applications or follow the "Making Google OAuth interactions safer by using more secure OAuth flows " (Feb. 2022) other recommendations.
The OP CoderForHire adds in the comments:
It seems like Google has allowed the use of
127.0.0.1with varying port numbers, which is a viable solution for desktop applications where the port number might change.Using
127.0.0.1with a random unused port obtained byGetRandomUnusedPort()as the loopback address for theHttpListeneris an approach that allows you to have a dynamic redirect URI for the OAuth 2.0 authorization code flow.Meaning:
HttpListenerthat listens on a random unused port on127.0.0.1.127.0.0.1with the port that yourHttpListeneris listening on.127.0.0.1with the specified port.HttpListenercaptures the redirect, and you can extract the authorization code from the redirect URI.This approach is effective for local development and for applications that are not publicly exposed. It is also a common pattern for OAuth 2.0 authorization in desktop applications.