2021-04-29

Google OAuth login in .NET Core / .NET 5 without using Identity

I'm having some trouble setting up Google as an external login provider in .NET 5 MVC app when not using Identity. External login using Facebook DOES work correctly, but external Google login DOES NOT. Twitter logins also do not work, but for the purposes of this question, lets focus on Google.

I have tested this on a small repro application that used Identity and both login providers worked correctly, so I'm assuming this is related to Identity not being used.

This is my current set up:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
            options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
            options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
        })
        .AddCookie(IdentityConstants.ApplicationScheme)
        .AddCookie(IdentityConstants.ExternalScheme)
        .AddFacebook(options =>
        {
            options.AppId = "{FacebookClientId}";
            options.AppSecret = "{FacebookClientSecret}";
        })
        .AddGoogle(options =>
        {
            options.ClientId = "{GoogleClientId}";
            options.ClientSecret = "{GoogleClientSecret}";
        });

    // ...
}

AccountController.cs

[HttpPost]
public ActionResult LoginExternal(string providerName, string returnUrl)
{
    var externalLoginCallbackUri = Url.Action("LoginExternalCallback", "Account", new { returnUrl });
    var properties = ConfigureExternalAuthenticationProperties(providerName, externalLoginCallbackUri);
    return Challenge(properties, providerName);
}

[HttpGet]
public async Task<ActionResult> LoginExternalCallback(string returnUrl)
{
    var loginInfo = await GetExternalLoginInfoAsync();
    if (loginInfo == null)
        return ExternalLoginFailed();

    // ...
    // gets no further
}

There are a couple of private methods in use above that are just copied from SignInManager:

private const string LoginProviderKey = "LoginProvider";
private const string XsrfKey = "XsrfId";

// SignInManager.GetExternalLoginInfoAsync()
private async Task<ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null)
{
    var auth = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
    var items = auth?.Properties?.Items;
    if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey))
    {
        return null;
    }

    if (expectedXsrf != null)
    {
        if (!items.ContainsKey(XsrfKey))
        {
            return null;
        }

        var userId = items[XsrfKey] as string;
        if (userId != expectedXsrf)
        {
            return null;
        }
    }

    var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
    var provider = items[LoginProviderKey] as string;
    if (providerKey == null || provider == null)
    {
        return null;
    }

    var schemes = (await _authenticationSchemeProvider.GetAllSchemesAsync())
        .Where(x => !string.IsNullOrEmpty(x.DisplayName));

    var providerDisplayName = schemes.FirstOrDefault(p => p.Name == provider)?.DisplayName
                              ?? provider;
    return new ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName)
    {
        AuthenticationTokens = auth.Properties.GetTokens(),
        AuthenticationProperties = auth.Properties
    };
}

// SignInManager.ConfigureExternalAuthenticationProperties()
private AuthenticationProperties ConfigureExternalAuthenticationProperties(string provider, string redirectUrl, string userId = null)
{
    var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
    properties.Items[LoginProviderKey] = provider;
    if (userId != null)
        properties.Items[XsrfKey] = userId;

    return properties;
}

When the user initiates a POST to /account/loginexternal?providerName=Google, they're shown the OAuth Google account selector, the user selects their account and a request is made to /account/loginexternalcallback.

At that point, when var auth = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme); is called the result indicates that the authentication was unsuccessful:

enter image description here

I'm a bit in the dark as to why this occurs. Is there a way to debug this process and determine why the authentication failed?

As far as I can tell, I've set up the credentials correctly in the Google Console (again, noting that this works when running using Identity):

enter image description here

Microsoft does have some docs on integrating social sign-in providers (Google specifically), but it doesn't delve in to the subject in enough detail to provide insight in to this problem. Other documentation relies on Identity, which I'm not using.

Am I doing something stupid here? The fact that Facebook login works is really throwing me off.



from Recent Questions - Stack Overflow https://ift.tt/3dYJ2PF
https://ift.tt/3e3efl4

No comments:

Post a Comment