5

I've gone through plenty of Google documentation and SO Q/A's but with no luck. I wonder if anyone has yet succesfully used the OpenId to OpenId Connect migration as advised by Google.

This is what we used to do:

IAuthenticationResponse response = _openid.GetResponse();
if (response != null) {
   //omitted for brevity       
} else {
   IAuthenticationRequest req = _openid.CreateRequest("https://www.google.com/accounts/o8/id");
   req.AddExtension(new ClaimsRequest
                    {
                        Country = DemandLevel.Request,
                        Email = DemandLevel.Request,
                        Gender = DemandLevel.Require,
                        PostalCode = DemandLevel.Require,
                        TimeZone = DemandLevel.Require
                    });
   req.RedirectToProvider();
}

That was done using a version of DotNetOpenAuth that dates back a few years. Because Google has deprecated OpenId authentication we are trying to move over to OpenID Connect. The key question here is: can I somehow get my hands on the OpenId identifier (in the form of https://www.google.com/accounts/o8/id?id=xyz) using the latest version of DotNetOpenAuth library or by any other means?

I have tried the latest DotNetOpenAuth and I can get it to work but it gives me a new Id (this was expected). I have also tried the Javascript way by using this URL (line breaks for readibility):

https://accounts.google.com/o/oauth2/auth?
    scope=openid%20profile%20email
    &openid.realm=http://localhost/palkkac/
    &client_id=//here is the client id I created in google developer console
    &redirect_uri=http://localhost/palkkac/someaspxpagehere
    &response_type=id_token%20token

I checked (using Fiddler) the realm value that we currently send using the old DotNetOpenAuth code and it is http://localhost/palkkac/. I've put the same realm in the url above. The redirect url starts with the realm value but it is not entirely the same.

When I redirect to a simple page that parses the id_token and decrypts it (using the https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=zyx endpoint) I get this:

audience    "client id is here"
email   "mikkark@gmail.com"
expires_in  3597
issued_at   //some numbers here
issued_to   "client id is here"
issuer  "accounts.google.com"
user_id     "here is a sequence of numbers, my id in the OpenID Connect format that is"
verified_email  true

So there is no sign of the openid_id field that you would expect to find here, though the whole structure of the message seems different from the Google docs, there is no field titled sub, for example. I wonder if I'm actually using the wrong endpoint, parameters or something?

What I have been reading is the migration guide: https://developers.google.com/accounts/docs/OpenID. I skipped step 2 because it seemed like an optional step. In step 3 the field openid_id is discussed and I would like to get that to work as a proof-of-concept first.

We registered the app on Google in order to create the client id etc. There are now also numerous allowed redirect url's as well as javascript origins listed in the Google dev console. Let me know if those might mess up the system and I'll post them here for review.

Side note: we are supposed to be moving our app behind a strictly firewalled environment where we would need to open ports in order to do this on the server side. Therefore, a client-side Javascript solution to access Google combined with HTTPS and redirecting the result to the server would be prefered (unless there are other issues that speak against this).

There are other resources on SO regarding this same issue, although all of these seem to use different libraries on the server side to do the job and nobody seems to have made any attempts at using Javascript:

  • Here (https://stackoverflow.com/questions/22842475/migrating-google-openid-to-openid-connect-openid-id-does-not-match) I think the problem was resolved by setting the realm to be the same as in the old OpenId2.0 flow. This does not seem to work in my case.
  • over here the openid_id field is also missing, but the problem here is more about how to request the id_token from Google using libraries other than DotNetOpenAuth.
  • and in here there seem to be similar problems getting Google to return the openid_id field.
Community
  • 1
  • 1
mikkark
  • 135
  • 9
  • We've also been unable to find a resolution for this problem after over a month of searching. – spadelives Nov 05 '14 at 20:46
  • @spadelives I see that you finally figured out how to send the realm parameter using Owin and ASP.NET MVC (refering to your SO question). But you are still not getting both id's, is that the case? – mikkark Nov 07 '14 at 06:17
  • yes, I'm getting both ids using the solution offered by cortex93 – spadelives Nov 08 '14 at 17:42
  • actually, I'm getting both ids but, unfortunately, not at the same time. The cortex solution provides the legacy id but the new id is not available in the idToken so there is no way to map the old and new in the SendAsync method. I've got the new id in the ExternalLoginCallback but the legacy id is not available from here. Still looking for way to either pass the legacy id out of the WebRequestHandler to the ExternalLoginCallback or to obtain the new id from within the SendAsync method. – spadelives Nov 12 '14 at 07:07
  • 1
    you can pass the legacy id to the ExternalLoginCallback as a session variable if you replace Debug.WriteLine(ex.ToString()); is cortex's code with HttpContext.Current.Session.Add("LegacyGoogleId", iclaim.Value); and then read the session variable from ExternalLoginCallback with this, var legacyGoogleId = Session["LegacyGoogleId"]; – spadelives Nov 12 '14 at 08:11
  • Hello, my name is Miguel and I work in the Google Identity team (which owns the OpenID endpoint). In case you have not managed to solve this issue yet, please contact me privately at gueles at google dot com. I will require a little extra info to debug the problem and once we find the solution I will post it in this thread. – Miguel Andres Nov 19 '14 at 16:53
  • @MiguelAndres: thanks for your reply, I will try to get my hands on this issue soon, recently I've been working on other stuff. I'll email you when I have this setup again (changes shelved at the moment). – mikkark Nov 25 '14 at 14:09
  • @spadelives: hmm... did you implement that part where you set the old Id into the session? I tried that but the Webrequesthandler does not have access to session automatically. I had to resort to the Application_PostAuthorizeRequest technique described [here](http://stackoverflow.com/a/17539008/1898051). The I found out that the session was null again after the first SendAsync call, so I had to store the session in a variable at the very beginning of the method. – mikkark Nov 28 '14 at 15:28
  • Yes but rather than passing the old identifier as a session variable and processing it in the ExternalLoginCallback method, I ended up simply updating the identifier field in the database as soon as I get the legacy Google id in the GoogleRequestHandler. – spadelives Nov 29 '14 at 17:28
  • I have implemented this using c# without any libraries. It is working well for me and I am receiving the open_id and sub fields in the token payload. I am using the endpoints published at https://accounts.google.com/.well-known/openid-configuration – 628426 Jan 24 '15 at 22:51

1 Answers1

2

You can use the GoogleAuthentication owin middleware.

app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
{
    SignInAsAuthenticationType = signAs,
    AuthenticationType = "Google",
    ClientId = "xxx.apps.googleusercontent.com",
    ClientSecret = "xx",
    CallbackPath = PathString.FromUriComponent("/oauth2callback"),
    Provider = new GoogleOAuth2AuthenticationProvider
    {
        OnApplyRedirect = context =>
        {
            context.Response.Redirect(context.RedirectUri + "&openid.realm=https://mydomain.com/"); // DotNetOpenAuth by default add a trailing slash, it must be exactly the same as before
        }
    },
    BackchannelHttpHandler = new MyWebRequestHandler()
}

Then, add a new class called MyWebRequestHandler:

public class MyWebRequestHandler : WebRequestHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var httpResponse = await base.SendAsync(request, cancellationToken);
            if (request.RequestUri == new Uri("https://www.googleapis.com/plus/v1/people/me")) return httpResponse;

            var configuration = await OpenIdConnectConfigurationRetriever.GetAsync("https://accounts.google.com/.well-known/openid-configuration", cancellationToken); // read the configuration to get the signing tokens (todo should be cached or hard coded)

            // google is unclear as the openid_id is not in the access_token but in the id_token
            // as the middleware dot not expose the id_token we need to parse it again
            var jwt = httpResponse.Content.ReadAsStringAsync().Result;
            JObject response = JObject.Parse(jwt);
            string idToken = response.Value<string>((object)"id_token"); 

            JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();

            try
            {
                SecurityToken token;
                var claims = tokenHandler.ValidateToken(idToken, new TokenValidationParameters()
                {
                    ValidAudience = "xxx.apps.googleusercontent.com",
                    ValidIssuer = "accounts.google.com",
                    IssuerSigningTokens = configuration.SigningTokens
                }, out token);

                var claim = claims.FindFirst("openid_id");
                // claim.Value will contain the old openid identifier
                if (claim != null) Debug.WriteLine(claim.Value);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
            return httpResponse;
        }
    }

If like me you found this not really straightforward, please help by upvoting this issue https://katanaproject.codeplex.com/workitem/359

cortex
  • 36
  • 1
  • If I'm not entirely mistaken this answer is ASP.NET MVC specific? Our app is plain ASP.NET, although the same general idea still must apply. – mikkark Nov 25 '14 at 14:07
  • Ok, so that piece of code is not spesific to MVC. I'm not very familiar with OWIN so I got that wrong. I can confirm that I can indeed get the old ID using the code from cortex. Now I would like to know how exactly to get this Owin code to work on ASP.NET (non-MVC) and "on the side" together with our current authentication mechanisms. In addition, our app is .net 4.0 so using Owin will require a framework upgrade. – mikkark Nov 27 '14 at 13:07
  • I managed to get this to work in plain ASP.NET too. We use a 3rd party SAML based authentication library which seems to mess things up a little. After I removed it I got this to work. This could work for as actually because we have two installations on different servers and one of those is using the SAML and the other will be using OAuth and no SAML. Still, I would like to get them to work together, at least to understand _why_ they don't work now. – mikkark Nov 28 '14 at 15:23
  • Hey @cortex, it looks like we can just follow [this](https://katanaproject.codeplex.com/SourceControl/network/forks/suhasj/Katana/contribution/7719#file_diff_tests/Katana.Sandbox.WebServer/Startup.cs) now? Want to update your answer? – Dan Friedman Mar 24 '15 at 22:18