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?
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
The call to
AddAuthentication
configures aDefaultScheme
, 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 theDefaultSignInScheme
later.The two calls to
AddCookie
add cookie-based authentication schemes for bothApplication
(our Application scheme) andExternal
(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
Let's break this down into the two actions:
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 theApplication
scheme but is attempting to access a page protected by theAuthorize
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 theGoogle
scheme. We do so using aChallengeResult
that is configured with theGoogle
scheme and aRedirectUrl
, which is used for returning to our own application code once the Google sign-in process completes. As the code shows, we return to:LoginCallback
This is where the
DefaultSignInScheme
from our call toAddAuthentication
becomes relevant. As part of the Google sign-in process completion, theDefaultSignInScheme
is used for setting a cookie that contains aClaimsPrincipal
representing the user as returned from Google (this is all handled behind the scenes). The first line of code inLoginCallback
grabs hold of thisClaimsPrincipal
instance, which is wrapped up inside anAuthenticateResult
that is first checked for success. If everything has been successful so far, we end up creating a newClaimsPrincipal
that contains whatever claims we need (taken from Google in this case) and then signing-in thatClaimsPrincipal
using theApplication
scheme. Lastly, we redirect to the page that caused our first challenge.I've created a GitHub repository that contains a complete example that I built in order to write this answer here.
In response to a couple of follow-up comments/questions in the comments below:
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.
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 inAddIdentity
.The call to
AuthenticateAsync
here doesn't know anything about Google - the Google-specific handling is configured by the call toAddGoogle
off ofAddAuthentication
inConfigureServices
. 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 toAddGoogle
, but that code is really just issuing a cookie in theExternal
scheme that stores the claims that came back from Google and then redirecting to ourLoginCallback
endpoint that we configured. If you add a call toAddFacebook
, a/sigin-facebook
endpoint will be configured to do something similar. The call toAuthenticateAsync
is really just rehydrating aClaimsPrincipal
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.If you don't want to use Entity Framework then you have to custom storage provider: https://docs.microsoft.com/en-us/aspnet/identity/overview/extensibility/overview-of-custom-storage-providers-for-aspnet-identity In case you want to use Entity Framework but get some error ( as you described), you can reference my source demo:
https://bitbucket.org/tuanv2t/net-core-demo/src/master/NetCoreDemo/GoogleLoginDemo/