Benji
Benji

Reputation: 36

How to use ASP.NET Core Identity framework in web app when only web api has access to database?

I am trying to separate my solution into three layers: Web APP -> Web API -> Data layer. I therefore don't want the app to have any access to the data layer. My problem is that I want to sign in with google in my web app(using identity framework), but I haven't managed to use the identity framework in the app without accessing the data layer.

I have the following four projects in my solution: Web application, Web Api, 2x class library(one for models and one for data access)

I am new to the idea of separating the application into layers so I might just look at this the wrong way.

Should the sign-in logic be in the api instead? Should I accept that the wep app has access to the data layer and use the DBContext in this one case? Is there a way to avoid using the DBContext when signing in with google in the app? Or is there a better solution?

I have used this youtube tutorial playlist for the following code: https://www.youtube.com/playlist?list=PL6n9fhu94yhVkdrusLaQsfERmL_Jh4XmU

This code is taken from Startup in the web app. DataContext is in my data library

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();


            services.AddDbContext<DataContext>(options =>
            {
                options.UseSqlServer(Configuration.GetConnectionString("Local"));
            });

            services.AddIdentity<IdentityUser, IdentityRole>()
                .AddEntityFrameworkStores<DataContext>();


            services.AddAuthentication()
            .AddGoogle(options =>
            {
                options.ClientId = "...";
                options.ClientSecret = "...";
            });
        } 

This is the code in my controller that lets your sign in with google

private readonly UserManager<IdentityUser> userManager;
        private readonly SignInManager<IdentityUser> signInManager;

        public AccountController(UserManager<IdentityUser> userManager,
                                SignInManager<IdentityUser> signInManager)
        {
            this.userManager = userManager;
            this.signInManager = signInManager;
        }

        public IActionResult Index()
        {
            return View();
        }

        [HttpGet]
        [AllowAnonymous]
        public async Task<IActionResult> Login(string returnUrl)
        {
            LoginViewModel loginViewModel = new LoginViewModel
            {
                ReturnUrl = returnUrl,
                ExternalLogins = (await signInManager.GetExternalAuthenticationSchemesAsync()).ToList()
            };
            return View(loginViewModel);
        }

        [HttpPost]
        public IActionResult ExternalLogin(string provider, string returnUrl)
        {
            var redirectCall = Url.Action("ExternalLoginCallback", "Account",
                new { ReturnUrl = returnUrl });

            var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectCall);

            return new ChallengeResult(provider, properties);
        }


        [AllowAnonymous]
        public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");


            LoginViewModel loginViewModel = new LoginViewModel
            {
                ReturnUrl = returnUrl,
                ExternalLogins = (await signInManager.GetExternalAuthenticationSchemesAsync()).ToList()
            };

            if (remoteError != null)
            {
                ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}");
                return View("Login", loginViewModel);
            }

            var info = await signInManager.GetExternalLoginInfoAsync();
            if(info == null)
            {
                ModelState.AddModelError(string.Empty, "Error loading login information.");
                return View("Login", loginViewModel);
            }

            var signInResult = await signInManager.ExternalLoginSignInAsync(info.LoginProvider,
                                    info.ProviderKey, isPersistent: false, bypassTwoFactor: true);

            if (signInResult.Succeeded)
                return LocalRedirect(returnUrl);
            else
            {
                var email = info.Principal.FindFirstValue(ClaimTypes.Email);

                if(email != null)
                {
                    var user = await userManager.FindByEmailAsync(email);
                    if(user == null)
                    {
                        user = new IdentityUser
                        {
                            UserName = info.Principal.FindFirstValue(ClaimTypes.Email),
                            Email = info.Principal.FindFirstValue(ClaimTypes.Email)
                        };

                        await userManager.CreateAsync(user);
                    }

                    await userManager.AddLoginAsync(user, info);
                    await signInManager.SignInAsync(user, isPersistent: false);

                    return LocalRedirect(returnUrl);
                }

                ViewBag.ErrorTitle = $"Email claim not recieved from: {info.LoginProvider}";
                ViewBag.ErrorMessage = "Please contact support";
                return View("Error");
            }
        }
    }

This is the Account view

@model ModelLibrary.ViewModels.LoginViewModel

@{
    ViewData["Title"] = "Login";
}

<h1>Login</h1>

@{
    if (Model.ExternalLogins.Count == 0)
    {
        <div>No External Logins configured</div>
    }
    else
    {
        <form method="post" asp-action="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl">
            <div>
                @foreach (var provider in Model.ExternalLogins)
                {
                    <button type="submit" class="btn btn-primary"
                            name="Provider" value="@provider.Name"
                            title="Login using your @provider.DisplayName account">
                        @provider.DisplayName
                    </button>
                }
            </div>
        </form>
    }
}
public class LoginViewModel
    {
        public string ReturnUrl { get; set; }
        public IList<AuthenticationScheme> ExternalLogins { get; set; }
    }

Upvotes: 1

Views: 934

Answers (1)

Nan Yu
Nan Yu

Reputation: 27578

One option is to use JWT authentication , user will pass credentials to web api , web api will access your database to validate the credential and return tokens to your client application , so that only web api could access the database and you can use ASP.NET Identity to manage users .

You can also use Identity Server 4 with ASP.NET Core Identity ,here is code sample .

Upvotes: 1

Related Questions