I have an ASP.NET 4.6 web app that I'm trying to add OpenId Connect using OWIN.
I added my Owin startup class and everything appears to be configured correctly, but the problem I'm having is the ASP Identity/Authenticated user never gets created. I end up with an endless loop, where the OpenId callback page redirects back to the original page, which then redirects to the login page, etc.
Here is my startup class:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Login.aspx"),
ExpireTimeSpan = TimeSpan.FromDays(7)
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = _clientId,
ClientSecret = _clientSecret,
Authority = _authority,
RedirectUri = _redirectUri, // LoginCallback
PostLogoutRedirectUri = "http://localhost:60624/Logout.aspx",
ResponseType = OpenIdConnectResponseType.CodeIdToken,
Scope = "openid profile email",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// Exchange code for access and ID tokens
var tokenClient = new TokenClient($"{_authority}/as/token.oauth2", _clientId, _clientSecret);
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, _redirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var userInfoClient = new UserInfoClient($"{_authority}/idp/userinfo.openid");
var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
var claims = new List<Claim>(userInfoResponse.Claims)
{
new Claim("id_token", tokenResponse.IdentityToken),
new Claim("access_token", tokenResponse.AccessToken)
};
n.AuthenticationTicket.Identity.AddClaims(claims);
//// create the identity
//var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
//System.Web.HttpContext.Current.GetOwinContext().Authentication.SignIn(new AuthenticationProperties
//{
// IsPersistent = true
//}, identity);
}
}
});
}
Here is the Login.aspx page:
protected void Page_Load(object sender, EventArgs e)
{
if (!Request.IsAuthenticated)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = Request["ReturnUrl"] ?? "Default.aspx" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
The page flow goes like this:
1) Request: http://localhost:60624/Page.aspx Response: 302 - redirect to Login.aspx
2) Request: http://localhost:60624/Login.aspx?ReturnUrl=%2FPage.aspx Response 302 - redirect to https://auth.myprovider.com
Some cookies set here on the response headers:
Set-Cookie: OpenIdConnect.nonce.KIsuj4RUmGKJIynLrkEScxBvGrZzkMo6ylZ%2F4lRknPM%3D=xxxxxxxxx; path=/; expires=Mon, 22-Apr-2019 14:12:00 GMT; HttpOnly Set-Cookie: OpenIdConnect.nonce.KIsuj4RUmGKJIynLrkEScxBvGrZzkMo6ylZ%2F4lRknPM%3D=yyyyyyyyy; expires=Mon, 22-Apr-2019 14:12:00 GMT; path=/; HttpOnly
3) Auth provider, sign-in, and it 302 redirects to /LoginCallback
4) Request: http://localhost:60624/LoginCallback Response 302 - redirect to /Page.aspx
Cookies that were set in step 2 are cleared here.
Set-Cookie: OpenIdConnect.nonce.KIsuj4RUmGKJIynLrkEScxBvGrZzkMo6ylZ%2F4lRknPM%3D=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT Set-Cookie: OpenIdConnect.nonce.KIsuj4RUmGKJIynLrkEScxBvGrZzkMo6ylZ%2F4lRknPM%3D=; expires=Thu, 01-Jan-1970 00:00:00 GMT; path=/
5) Back to Page.aspx, user not authenticated; Goto step 1
I've done some debugging, and the AuthorizationCodeReceived fires on the Startup, and the backend successfully calls the User Info endpoint. I've tried to call System.Web.HttpContext.Current.GetOwinContext().Authentication.SignIn() from that Notification, but that doesn't seem to do anything.
At this point, I'm stuck. Why is the authentication cookie for the user identity not being set? It seems like this is supposed to happen automatically. Am I supposed to manually create this myself? (How can I manually create a authentication cookie instead of the default method?)
EDIT: After reviewing @Zaxxon's reply, I was able to get it working. There were 2 things wrong in the AuthorizationCodeReceived notification
- I needed to create the ClaimsIdentity. In my original code I submitted above, I had commented out this, but it was also incorrect.
- I had to replace the AuthenticationTicket with a new one with the new identity I just created. Then add the claims to this new Identity.
Here is the working code:
ClaimsIdentity identity = new ClaimsIdentity(DefaultAuthenticationTypes.ApplicationCookie, ClaimTypes.GivenName, ClaimTypes.Role);
n.AuthenticationTicket = new AuthenticationTicket(identity, n.AuthenticationTicket.Properties);
n.AuthenticationTicket.Identity.AddClaims(claims);