Reputation: 36
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
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