Google OAuth suddenly failing with Bad Request 400

Google OAuth2 code that has been working for the past 2 years has suddenly started failing giving a Bad Request 400 error. The Json error array is empty. I tried running the WebAuthTests but I’m encountering the same issue with your sample as well.

It appears to be due to a change Google made to their OAuth endpoints, most likely:

https://github.com/GoogleCloudPlatform/google-cloud-common/issues/260

I’m still looking into a fix.

To give you an update, you can use this modified GoogleOAuth2Provider class below which avoids DotNetOpenAuth for retrieving the AccessToken:

public class GoogleOAuth2Provider : OAuth2Provider
{
    public const string Name = "GoogleOAuth";

    public const string Realm = "https://oauth2.googleapis.com/token";

    public GoogleOAuth2Provider(IAppSettings appSettings)
        : base(appSettings, Realm, Name)
    {
        this.AuthorizeUrl = this.AuthorizeUrl ?? Realm;
        this.AccessTokenUrl = this.AccessTokenUrl ?? "https://oauth2.googleapis.com/token";
        this.UserProfileUrl = this.UserProfileUrl ?? "https://www.googleapis.com/oauth2/v2/userinfo";

        if (this.Scopes.Length == 0)
        {
            this.Scopes = new[] {
                "https://www.googleapis.com/auth/userinfo.profile",
                "https://www.googleapis.com/auth/userinfo.email"
            };
        }

        this.VerifyAccessToken = OnVerifyAccessToken;
    }

    public string VerifyAccessTokenUrl { get; set; } = "https://www.googleapis.com/oauth2/v2/tokeninfo?access_token={0}";

    public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
    {
        var httpRequest = authService.Request;
        var code = httpRequest.QueryString[Keywords.Code];
        if (code == null)
            return base.Authenticate(authService, session, request);

        var tokens = Init(authService, ref session, request);

        try
        {
            var accessTokenUrl = $"{AccessTokenUrl}?code={code}&client_id={ConsumerKey}&client_secret={ConsumerSecret}&redirect_uri={this.CallbackUrl.UrlEncode()}&grant_type=authorization_code";
            var contents = AccessTokenUrlFilter(this, accessTokenUrl).PostToUrl("");
            var authInfo = JsonObject.Parse(contents);

            var accessToken = authInfo["access_token"];

            return AuthenticateWithAccessToken(authService, session, tokens, accessToken)
                   ?? authService.Redirect(SuccessRedirectUrlFilter(this, session.ReferrerUrl.SetParam("s", "1"))); //Haz Access!
        }
        catch (WebException we)
        {
            //string errorBody = we.GetResponseBody();
            //errorBody.Print();
            var statusCode = ((HttpWebResponse)we.Response).StatusCode;
            if (statusCode == HttpStatusCode.BadRequest)
            {
                return authService.Redirect(FailedRedirectUrlFilter(this, session.ReferrerUrl.SetParam("f", "AccessTokenFailed")));
            }
        }

        //Shouldn't get here
        return authService.Redirect(FailedRedirectUrlFilter(this, session.ReferrerUrl.SetParam("f", "Unknown")));
    }

    public bool OnVerifyAccessToken(string accessToken)
    {
        var url = VerifyAccessTokenUrl.Fmt(accessToken);
        var json = url.GetJsonFromUrl();
        var obj = JsonObject.Parse(json);
        var issuedTo = obj["issued_to"];
        return issuedTo == ConsumerKey;
    }

    protected override Dictionary<string, string> CreateAuthInfo(string accessToken)
    {
        var url = this.UserProfileUrl.AddQueryParam("access_token", accessToken);
        string json = url.GetJsonFromUrl();
        var obj = JsonObject.Parse(json);
        var authInfo = new Dictionary<string, string>
        {
            { "user_id", obj["id"] }, 
            { "username", obj["email"] }, 
            { "email", obj["email"] }, 
            { "name", obj["name"] }, 
            { "first_name", obj["given_name"] }, 
            { "last_name", obj["family_name"] },
            { "gender", obj["gender"] },
            { "birthday", obj["birthday"] },
            { "link", obj["link"] },
            { "picture", obj["picture"] },
            { "locale", obj["locale"] },
            { AuthMetadataProvider.ProfileUrlKey, obj["picture"] },
        };
        return authInfo;
    }
}

I’m still working on a new GoogleAuthProvider which avoids DotNetOpenAuth completely, I’ll let you know as soon as it’s available.

Getting a 404 error on this before authenticating
https://oauth2.googleapis.com/token?client_id=XXXX&redirect_uri=https%3A%2F%2Flocalhost%3A44300%2Fauth%2FGoogleOAuth&state=svAkWuRxnR2cI2i2nQ8GLA&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&response_type=code was not found on this server.

Also you have a error in var accessTokenUrl
695995a1cf8d88c9d143ee3b93adb32dquot

Not sure it’s working for me, the full source code is at GoogleOAuth2Provider.cs.

I’ve also created a new GoogleAuthProvider in this commit which doesn’t have the DotNetOpenAuth dependency and will replace the hybrid GoogleOAuth2Provider above.

To switch to the new provider you’ll need to change your AppSettings to use oauth.google.*, e.g:

    <add key="oauth.google.ConsumerKey" value="..." />
    <add key="oauth.google.ConsumerSecret" value="..." />

and your Google OAuth App will need to redirect to /auth/google redirect url.

These changes are available from v5.2.1 that’s now available on MyGet.

I’ve also updated the MVC project to use the new GoogleAuthProvider and deployed a working example at: http://mvc.servicestack.net

FYI the latest GoogleOAuth2Provider.cs should resolve the 404 error, which is now on MyGet.

One slight issue - for some reason the provider is populating accesstokensecret instead of accesstoken. Unsure if there’s a specific change or distinction that mandates the change. Within the code itself they seem to be used interchangeably.

Yeah some Auth Providers have a pair of access tokens, when there’s only one we’re populating it in AccessTokenSecret.

I’m going to leave it populating AccessTokenSecret to be consistent with all other OAuth Providers in ServiceStack/Auth

I grabbed 5.4.1 from your myget server and still get the same error message. Have to admit I’m not overly familiar with open auth concepts, but I just plugged in the new package, recompiled, and deployed.

Response Status
Error CodeWebExceptionMessageThe remote server returned an error: (400) Bad Request.Stack Trace[Authenticate: 9/25/2018 2:48:27 PM]: [REQUEST: {provider:GoogleOAuth,state:z-qe42KiOhvJce44YxiXKg}] System.Net.WebException: The remote server returned an error: (400) Bad Request. at System.Net.HttpWebRequest.GetResponse() at DotNetOpenAuth.Messaging.StandardWebRequestHandler.GetResponse(HttpWebRequest request, DirectWebRequestOptions options)Errors

That looks like the old Error from the previous version of GoogleOAuth. Try using the new GoogleAuthProvider from by comment above which is also available from the latest v5.4.0 release on NuGet.

Using version 5.4 I think I’m getting closer. Now I see this error message:
Error CodeArgumentNullException
MessageValue cannot be null. Parameter name: clientSecret

In my web.config:

<add key="oauth.RedirectUrl" value="https://authenticate.blah.com/" />
<add key="oauth.CallbackUrl" value="https://authenticate.blah.com/auth/{0}" />
 
<!-- Create Google App at: https://console.developers.google.com/ -->
<add key="oauth.google.ConsumerKey" value="blahblahblah" />
<add key="oauth.google.ConsumerSecret" value="abc123xyz" />

My target looks like this:

<li><a id="google" href="#" onclick="popUpLogin('/auth/googleoauth', '@ReturnUrl',  '@RFF')"><span class="text">Google</span></a></li>

I’m looking at the web.config file at


and I think I’m using the correct keys?

The Auth name for the new GoogleAuthProvider is google so all html links would need to be to /auth/google.