Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to implement google login in .net core without an entityframework provider

I am implementing Google login for my .net core site.

In this code

var properties = signInManager.ConfigureExternalAuthenticationProperties("Google", redirectUrl);      
return new ChallengeResult("Google", properties); 

I need a signInManager which is (by the code example) this:

private SignInManager<AppUser> signInManager;

I inject it via the constructor, and then I get this error:

Unable to resolve service for type 'Microsoft.AspNetCore.Identity.SignInManager1[AppUser]' while attempting to activate 'AccountController'.

Googling learnt that I should include this

services.AddIdentity<AppUser, IdentityRole>()
    .AddDefaultTokenProviders();`

But that gives me this error:

Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore1[AppUser]' while attempting to activate 'Microsoft.AspNetCore.Identity.AspNetUserManager1[AppUser]'.

And at that moment, I get the advice to add this:

.AddEntityFrameworkStores<ApplicationDbContext>()

But then I'm lost, because why does the SignInManager need a IUserStore, and should I add a UserStore and a DBContext and an EntityFramework store, when I will not be using that (for my Google login)?

So the question is: can I also do my Google login without the Entityframework store?

like image 353
Michel Avatar asked Dec 06 '18 14:12

Michel


People also ask

How to integrate Google login feature in ASP NET Core Identity?

How to integrate Google login feature in ASP.NET Core Identity There are 2 main steps to do when creating Google login feature. 1. Create a new Project in Google Console. 2. Use Google APIs to communicate with your Project located in Google Console. Google Console, is the place where you can create your Google Projects.

Should you replace Entity Framework with ASP NET identity?

Of course, Entity Framework isn’t for everyone, and ASP.NET Identity’s extensibility made it easy to remove the Entity Framework integration and replace it with something else. With the release of ASP.NET Core, Microsoft also released a refreshed version of ASP.NET Identity called — wait for it — ASP.NET Core Identity.

How to integrate ASP NET Core Identity with OAuth?

ASP.NET Core Identity has a built in support for authentication service that works on OAuth like Google, Facebook, Microsoft, LinkedIn, Twitter, etc. There are extension methods to register them in your application. First you need to install the package called Microsoft.AspNetCore.Authentication.Google from NuGet.

How do I integrate Google Sign-in with my Web App?

Add the Microsoft.AspNetCore.Authentication.Google NuGet package to the app. Follow the guidance in Integrating Google Sign-In into your web app (Google documentation). Go to Google API & Services. A Project must exist first, you may have to create one. Just a name is required. Once a project is selected, you will enter the Dashboard.


1 Answers

If all you want to do is sign-in with Google, there's no need for SignInManager, UserManager or ASP.NET Core Identity itself. To achieve this, we first need to configure the Authentication services. Here's the relevant code for this, which I'll explain after:

Startup.cs

services
    .AddAuthentication(o =>
    {
        o.DefaultScheme = "Application";
        o.DefaultSignInScheme = "External";
    })
    .AddCookie("Application")
    .AddCookie("External")
    .AddGoogle(o =>
    {
        o.ClientId = ...;
        o.ClientSecret = ...;
    });
  • The call to AddAuthentication configures a DefaultScheme, which ends up being used as both the Application scheme and the Challenge scheme. The Application scheme is used when attempting to authenticate the user (are they signed in?). The Challenge scheme is used when a user is not signed in but the application wants to provide the option to do so. I'll discuss the DefaultSignInScheme later.

  • The two calls to AddCookie add cookie-based authentication schemes for both Application (our Application scheme) and External (our SignIn scheme). AddCookie can also take a second argument, that allows for configuration of e.g. the corresponding cookie's lifetime, etc.

With this in place, the challenge process will redirect the user over to /Account/Login (by default - this can be configured via the cookie authentication options too). Here's a controller implementation that handles the challenge process (again, I'll explain after):

AccountController.cs

public class AccountController : Controller
{
    public IActionResult Login(string returnUrl)
    {
        return new ChallengeResult(
            GoogleDefaults.AuthenticationScheme,
            new AuthenticationProperties
            {
                RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl })
            });
    }

    public async Task<IActionResult> LoginCallback(string returnUrl)
    {
        var authenticateResult = await HttpContext.AuthenticateAsync("External");

        if (!authenticateResult.Succeeded)
            return BadRequest(); // TODO: Handle this better.

        var claimsIdentity = new ClaimsIdentity("Application");

        claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
        claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.Email));

        await HttpContext.SignInAsync(
            "Application",
            new ClaimsPrincipal(claimsIdentity));

        return LocalRedirect(returnUrl);
    }
}

Let's break this down into the two actions:

  1. Login

    In order to arrive at the Login action, the user will have been challenged. This occurs when the user is not signed in using the Application scheme but is attempting to access a page protected by the Authorize attribute (or similar). Per your requirement, if the user is not signed in, we want to sign them in using Google. In order to achieve that, we issue a new challenge, this time for the Google scheme. We do so using a ChallengeResult that is configured with the Google scheme and a RedirectUrl, which is used for returning to our own application code once the Google sign-in process completes. As the code shows, we return to:

  2. LoginCallback

    This is where the DefaultSignInScheme from our call to AddAuthentication becomes relevant. As part of the Google sign-in process completion, the DefaultSignInScheme is used for setting a cookie that contains a ClaimsPrincipal representing the user as returned from Google (this is all handled behind the scenes). The first line of code in LoginCallback grabs hold of this ClaimsPrincipal instance, which is wrapped up inside an AuthenticateResult that is first checked for success. If everything has been successful so far, we end up creating a new ClaimsPrincipal that contains whatever claims we need (taken from Google in this case) and then signing-in that ClaimsPrincipal using the Application scheme. Lastly, we redirect to the page that caused our first challenge.

In response to a couple of follow-up comments/questions in the comments below:

Can I conclude that the SignInManager and UserManager are only used when using authentication with a database?

In some ways, yes, I think that's fair. Although it is possible to implement an in-memory store, it doesn't really make much sense with no persistence. However, the real reason not to use these classes in your situation is simply because you do not need a local user account for representing a user. That goes hand-in-hand with persistence, but it's worth making the distinction.

And what very different code from what I read in the book (which I used for setting up my Google login) and all the other answers I've read.

The documentation and the books cover the most common use-case, whereby you do want to store local users that can be linked to external accounts such as Google, etc. If you look at the SignInManager source, you'll see that it's really just sitting on top of the kind of code I've shown above (e.g. here and here). Other code can be found in the Default UI (e.g. here) and in AddIdentity.

I assume the LoginCallback gets called by Google. Does the HttpContext.AuthenticateAsync know how to check the data Google sends me? And as it's name is so generic, it looks like it knows how to do that for all external providers?

The call to AuthenticateAsync here doesn't know anything about Google - the Google-specific handling is configured by the call to AddGoogle off of AddAuthentication in ConfigureServices. After redirecting to Google for sign-in, we actually come back to /signin-google in our application. Again, this is handled thanks to the call to AddGoogle, but that code is really just issuing a cookie in the External scheme that stores the claims that came back from Google and then redirecting to our LoginCallback endpoint that we configured. If you add a call to AddFacebook, a /sigin-facebook endpoint will be configured to do something similar. The call to AuthenticateAsync is really just rehydrating a ClaimsPrincipal from the cookie that was created by e.g. the /signin-google endpoint, in order to retrieve the claims.

It's also worth noting that the Google/Facebook sign-in process is based on the OAuth 2 protocol, so it's kind of generic in itself. If you were to need support for more than just Google, you would just issue the challenge against the required scheme rather than hardcoding it to Google as I've done in the example. It's also possible to add additional properties to the challenge in order to be able to determine which provider was used when your LoginCallback endpoint is reached.


I've created a GitHub repository that contains a complete example that I built in order to write this answer here.

like image 98
Kirk Larkin Avatar answered Oct 08 '22 06:10

Kirk Larkin