I am using IdentityServer3 to handle user authentication in a asp mvc web site.
The login screen is hosted in a view in the application itself (not using the Identity Server implicit flow). I also offer a "Login with Google" option, which the user chooses by clicking a button on the login screen.
Any page which has [Authorize] should redirect an unauthenticated user to the login screen:
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
LoginPath = new PathString("/Account/Login")
});
}
The Account controller outline is shown below. The 'Forms Authentication' scenario will work (as described here).
The 'Login with Google' button works as described here (skipping the ID server login page, going direct to Google) by specifying an 'acr_values' param.
How should I deal with the callback from the OpenID Connect after I log in with Google? I tried adding the OpenId Connect middleware but it doesn't play well with the 'LoginPath' feature on Cookie Authentication: unauthenticated users are now redirected to the ID Server login screen not my local login screen.
I can't see anything in the IdentityServer samples that addresses this scenario.
public class AccountController : Controller
{
[HttpGet]
public ActionResult Login()
{
ViewBag.GoogleLogin = CreateLoginUrl("Google");
return View();
}
[HttpPost]
public ActionResult Login(LoginViewModel vm)
{
// Call IdentityServer here with credentials
// Validate token and do the Owin Authentication SignIn
// Redirect to 'ReturnUrl'
// If errors:
return View(vm);
}
public ActionResult Callback()
{
// What goes here??
return new RedirectResult("/");
}
private string CreateLoginUrl(string provider)
{
var state = Guid.NewGuid().ToString("N");
var nonce = Guid.NewGuid().ToString("N");
var request = new AuthorizeRequest(new Uri("https://localhost:44312/connect/authorize"));
var startUrl = request.CreateAuthorizeUrl(
clientId: "mvc",
responseType: "id_token token",
scope: "openid profile roles sampleApi",
redirectUri: "https://localhost:44319/Account/Callback",
state: state,
acrValues: "idp:" + provider,
nonce: nonce);
return startUrl;
}
}
I sidestepped the issue of conflicting authentication providers by using the Map IAppBuilder extension to only apply the Open Id Connect Middleware to a single endpoint. This way the Social login flow is handled entirely by middleware, and the authentication cookie is shared by both authentication providers.
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
LoginPath = new PathString("/Account/Login")
});
app.Map(new PathString("/Account/SocialLogin"), ctx =>
{
ctx.UseOpenIdConnectAuthentication(Options());
});
}
And the AccountController bit:
public class AccountController : Controller
{
[HttpGet]
public ActionResult Login(string returnUrl)
{
return View(new LoginViewModel { ReturnUrl = returnUrl });
}
[HttpPost]
public async Task<ActionResult> Login(LoginViewModel vm)
{
if (!ModelState.IsValid)
{
return View(vm);
}
// Call IdentityServer here with credentials
TokenResponse token = await GetToken(vm.UserName, vm.Password);
// Validate token and do the Owin Authentication SignIn
// Redirect to 'ReturnUrl'
await SignInAsync(token);
return new RedirectResult(vm.ReturnUrl);
}
[Authorize]
public ActionResult SocialLogin(string returnUrl)
{
return new RedirectResult(returnUrl);
}
// Some helpers omitted
}