Google PeopleAPI & HttpListener Redirect URI
-
We have an an app that allows the user to import their Google contacts. We're modifying it because Google changed their auth process. The idea is to call the Google Auth page and use an HTTPListener to listen on a port for the redirect URI. I went to the Google API Console [^]and set the Redirect URI to "https://127.0.0.1". We can't hardcode a port here. There's a WPF sample from Google[^] here. Copy to a WPF window to see how it works. So 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? Here's my code:
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 https://127.0.0.1 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
-
We have an an app that allows the user to import their Google contacts. We're modifying it because Google changed their auth process. The idea is to call the Google Auth page and use an HTTPListener to listen on a port for the redirect URI. I went to the Google API Console [^]and set the Redirect URI to "https://127.0.0.1". We can't hardcode a port here. There's a WPF sample from Google[^] here. Copy to a WPF window to see how it works. So 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? Here's my code:
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 https://127.0.0.1 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
The listener listens on a single port. The URL MUST specify the port. There is no other option. That is fundamental to how HTTP and the underlying TCP/IP works. After that I am not sure where your confusion lies. You do realize that you can and should use a static port right? Same port for all customers/clients? The additional information in the URL differentiates each of them. Also presumably the code above is just test code, because you would need a fixed service listener that responds to those requests. Can't create the listener in the same code that initiates the request.
-
The listener listens on a single port. The URL MUST specify the port. There is no other option. That is fundamental to how HTTP and the underlying TCP/IP works. After that I am not sure where your confusion lies. You do realize that you can and should use a static port right? Same port for all customers/clients? The additional information in the URL differentiates each of them. Also presumably the code above is just test code, because you would need a fixed service listener that responds to those requests. Can't create the listener in the same code that initiates the request.
jschell wrote:
You do realize that you can and should use a static port right?
That's the problem. We can't specify a port in the Google Console. The port needs to be random at runtime
In theory, theory and practice are the same. But in practice, they never are.” If it's not broken, fix it until it is. Everything makes sense in someone's mind.
-
jschell wrote:
You do realize that you can and should use a static port right?
That's the problem. We can't specify a port in the Google Console. The port needs to be random at runtime
In theory, theory and practice are the same. But in practice, they never are.” If it's not broken, fix it until it is. Everything makes sense in someone's mind.
Kevin Marois wrote:
We can't specify a port in the Google Console.
Is the problem that there is no separate input field for the port number? In that case, have you tried putting the port number into the URL? http://127.0.01:5687 5687 is the port number.
The difficult we do right away... ...the impossible takes slightly longer.
-
Kevin Marois wrote:
We can't specify a port in the Google Console.
Is the problem that there is no separate input field for the port number? In that case, have you tried putting the port number into the URL? http://127.0.01:5687 5687 is the port number.
The difficult we do right away... ...the impossible takes slightly longer.
No, we are not allowed to use a static port
In theory, theory and practice are the same. But in practice, they never are.” If it's not broken, fix it until it is. Everything makes sense in someone's mind.
-
jschell wrote:
You do realize that you can and should use a static port right?
That's the problem. We can't specify a port in the Google Console. The port needs to be random at runtime
In theory, theory and practice are the same. But in practice, they never are.” If it's not broken, fix it until it is. Everything makes sense in someone's mind.
Kevin Marois wrote:
The port needs to be random at runtime
Then you will need to put in in the URL. As I noted the URL allows for a port in it. So you can indeed put it in there. But that means you will need to fly the server every time. No idea what that will do to your security model. Certainly won't work without a range since some ports are required and some are considered high security risks (even when something else it attached.) And for some reason that it will not accept a perfectly valid URL with a port then you are done. Your solution will not work. Summarizing above. 1. You say the server port must be random. 2. You must include the port in the URL 3. Either 1 is false, or 2 must be true or your solution will not work
-
Kevin Marois wrote:
The port needs to be random at runtime
Then you will need to put in in the URL. As I noted the URL allows for a port in it. So you can indeed put it in there. But that means you will need to fly the server every time. No idea what that will do to your security model. Certainly won't work without a range since some ports are required and some are considered high security risks (even when something else it attached.) And for some reason that it will not accept a perfectly valid URL with a port then you are done. Your solution will not work. Summarizing above. 1. You say the server port must be random. 2. You must include the port in the URL 3. Either 1 is false, or 2 must be true or your solution will not work
I agree, and I'm not sure how else I would do this. What it comes down to is waiting for the OAuth to complete and getting the reponse url. Do you know of any other way of doing this other than using an HTTPListener?
In theory, theory and practice are the same. But in practice, they never are.” If it's not broken, fix it until it is. Everything makes sense in someone's mind.
-
Kevin Marois wrote:
The port needs to be random at runtime
Then you will need to put in in the URL. As I noted the URL allows for a port in it. So you can indeed put it in there. But that means you will need to fly the server every time. No idea what that will do to your security model. Certainly won't work without a range since some ports are required and some are considered high security risks (even when something else it attached.) And for some reason that it will not accept a perfectly valid URL with a port then you are done. Your solution will not work. Summarizing above. 1. You say the server port must be random. 2. You must include the port in the URL 3. Either 1 is false, or 2 must be true or your solution will not work
Update on this. Here's some things I found... First, on the Google People API Console[^], we had a Web Application set up, which I now think is wrong. We want a Desktop App since this is a WPF app. One key difference is that the Web Application page shows fields for Authorized redirect URIs[^]. This one has 'HTTPS://127.0.0.1' in it. The Desktop App [^] does NOT have redirect URIs. But when I downloaded the credentials JSON, it contains
"redirect_uris":["http://localhost"]}
Then, in my code I have
public async Task TryAuthorizeAsync()
{
// code ommitted
...// HTTOListener callback URI. string listenerURI = $"https://{IPAddress.Loopback}:{GetRandomUnusedPort()}/"; //==== Listener callback on random port // Auth callback URL string callbackURI = "http://localhost"; //==== SET TO LOCALHOST RaiseStatusChanged($"listening on {listenerURI}"); try { \_callbackListener.Prefixes.Add(listenerURI); \_callbackListener.Start(); } catch (Exception e) { throw e; } // Create the authorization request. var authorizationRequest = $"{AuthorizationEndpoint}?response\_type=code&" + $"scope=openid%20profile&" + $"redirect\_uri={Uri.EscapeDataString(callbackURI)}&" + //===== Uses default from downloade JSON $"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 var callback = new AsyncCallback(result => ListenerCallback(result, state, codeVerifier, lis
-
Update on this. Here's some things I found... First, on the Google People API Console[^], we had a Web Application set up, which I now think is wrong. We want a Desktop App since this is a WPF app. One key difference is that the Web Application page shows fields for Authorized redirect URIs[^]. This one has 'HTTPS://127.0.0.1' in it. The Desktop App [^] does NOT have redirect URIs. But when I downloaded the credentials JSON, it contains
"redirect_uris":["http://localhost"]}
Then, in my code I have
public async Task TryAuthorizeAsync()
{
// code ommitted
...// HTTOListener callback URI. string listenerURI = $"https://{IPAddress.Loopback}:{GetRandomUnusedPort()}/"; //==== Listener callback on random port // Auth callback URL string callbackURI = "http://localhost"; //==== SET TO LOCALHOST RaiseStatusChanged($"listening on {listenerURI}"); try { \_callbackListener.Prefixes.Add(listenerURI); \_callbackListener.Start(); } catch (Exception e) { throw e; } // Create the authorization request. var authorizationRequest = $"{AuthorizationEndpoint}?response\_type=code&" + $"scope=openid%20profile&" + $"redirect\_uri={Uri.EscapeDataString(callbackURI)}&" + //===== Uses default from downloade JSON $"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 var callback = new AsyncCallback(result => ListenerCallback(result, state, codeVerifier, lis
127.0.0.1 and localhost are the same thing. And neither is going to connect to anything but the box, the exact box, that originated the request. For the internet an actual addressable IP must be public. Local networks can use private IP addresses. HTTP uses IP addresses. Host names just are resolved to a IP address. If the above is new to you then you can start by reading the following IP address - Wikipedia[^]
-
127.0.0.1 and localhost are the same thing. And neither is going to connect to anything but the box, the exact box, that originated the request. For the internet an actual addressable IP must be public. Local networks can use private IP addresses. HTTP uses IP addresses. Host names just are resolved to a IP address. If the above is new to you then you can start by reading the following IP address - Wikipedia[^]
It's not new. The console won't accept wildcards
http://127.0.0.1:*/*
is not allowed. So, if the console requires a port #, and we are not allowed to use a static port #, then I don't see how this can work. The question still is, how is there sample app doing it? If their app's redirectURI is 127.0.0.1 (according to the credentials JSON), and the client code is using a random port, then how does it work?? [UPDATE] For some reason I can't explain, this now works
string listenerURI = $"http ://127.0.0.1:{GetRandomUnusedPort()}/";
In theory, theory and practice are the same. But in practice, they never are.” If it's not broken, fix it until it is. Everything makes sense in someone's mind.
-
It's not new. The console won't accept wildcards
http://127.0.0.1:*/*
is not allowed. So, if the console requires a port #, and we are not allowed to use a static port #, then I don't see how this can work. The question still is, how is there sample app doing it? If their app's redirectURI is 127.0.0.1 (according to the credentials JSON), and the client code is using a random port, then how does it work?? [UPDATE] For some reason I can't explain, this now works
string listenerURI = $"http ://127.0.0.1:{GetRandomUnusedPort()}/";
In theory, theory and practice are the same. But in practice, they never are.” If it's not broken, fix it until it is. Everything makes sense in someone's mind.
The redirect_uri is a URI. Which means, as I suggested you can put an actual full URL in there including the port. The example in the following shows a less than trivial URL, but one to which a port could be added easily. Using OAuth 2.0 for Web Server Applications | Authorization | Google for Developers[^] Since it is a query parameter the full URL would need to be correctly escaped in that expression.