Reputation: 103
We have a .net MVC web application that uses ASP.NET Membership for user authentication. We also issue bearer tokens for our mobile application. I am attempting to add support for SSO using okta as an example. I have created a application in Okta and am attempting to sign into our application. My current flow is as follows:
It seems that after the login redirect and I set the correct claims, the result should be that a cookie should be issued and returned on the response to denote that the user is signed in. I'm not sure what I'm missing here, but if anyone can point me in the right direction, or explain what is happening I would very much appreciate it!
Below is a snippet from our Startup.cs:
public void ConfigureAuth(IAppBuilder app, bool allowInsecureHttp = false)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(OwinIdentityDbContext.Create);
app.CreatePerOwinContext<IdentityUserManager>(IdentityUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = GlobalConstants.Settings.OktaClientId,
ClientSecret = GlobalConstants.Settings.OktaClientSecret,
Authority = GlobalConstants.Settings.OktaDomain,
AuthenticationType = "okta", // This is basically a "name" for this open id configuration
RedirectUri = GlobalConstants.Settings.OktaRedirectUri,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
Scope = $"{OpenIdConnectScope.OpenIdProfile} {OpenIdConnectScope.Email}", // Get the profile and email
PostLogoutRedirectUri = GlobalConstants.Settings.OktaPostLogoutRedirectUri,
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// Exchange code for access and ID tokens
var tokenClient = new TokenClient(GlobalConstants.Settings.OktaDomain + "/v1/token", GlobalConstants.Settings.OktaClientId, GlobalConstants.Settings.OktaClientSecret);
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, GlobalConstants.Settings.OktaRedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var userInfoClient = new UserInfoClient(GlobalConstants.Settings.OktaDomain + "/v1/userinfo");
var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
var claims = new List<Claim>();
claims.AddRange(userInfoResponse.Claims);
claims.Add(new Claim("id_token", tokenResponse.IdentityToken));
claims.Add(new Claim("access_token", tokenResponse.AccessToken));
if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
{
claims.Add(new Claim("refresh_token", tokenResponse.RefreshToken));
}
// Add custom user claims here
#region Add custom claims
var email = claims.Find(_ => _.Type == "email")?.Value;
claims.Add(new Claim(ClaimTypes.Name, email));
claims.Add(new Claim(ClaimTypes.NameIdentifier, email));
// Hardcoded for now. Discover these for real later
claims.Add(new Claim(CustomClaimTypes.OurUserId, "{userId}"));
claims.Add(new Claim(CustomClaimTypes.OurOrganizationId, "{orgId}"));
#endregion
// https://stackoverflow.com/questions/55797063/asp-net-owin-openid-connect-not-creating-user-authentication
var identity = new ClaimsIdentity(DefaultAuthenticationTypes.ApplicationCookie, ClaimTypes.Name, ClaimTypes.Role);
n.AuthenticationTicket = new AuthenticationTicket(identity, n.AuthenticationTicket.Properties);
n.AuthenticationTicket.Identity.AddClaims(claims);
return;
},
RedirectToIdentityProvider = n =>
{
// If signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var idTokenClaim = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenClaim != null)
{
n.ProtocolMessage.IdTokenHint = idTokenClaim.Value;
}
}
return Task.CompletedTask;
}
},
});
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieManager = new SystemWebCookieManager(),
LoginPath = new PathString("/"),
SlidingExpiration = true,
CookieSecure = GlobalConstants.Settings.IsEnvironmentDev ? CookieSecureOption.SameAsRequest : CookieSecureOption.Always,
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = context =>
{
if (IsClientPortalRequest(context.Request))
{
context.Response.Redirect("/public/ClientPortalLogin");
}
else if (!IsAjaxRequest(context.Request) && !IsPublicApiRequest(context.Request) && !IsWopiRequest(context.Request))
{
context.Response.Redirect(context.RedirectUri);
}
}
}
});
// For Two Factor Log In
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(10));
OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = allowInsecureHttp, // NOTE: Only for unit tests
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomBearerTokenProvider()
};
// Configure Bearer Token Generation
app.UseOAuthAuthorizationServer(OAuthOptions);
// Needed to Authenticate Bearer Tokens
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
// Set Cookies are the default type
app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);
}
Here is a screenshot of our okta application configuration:
Upvotes: 0
Views: 1904
Reputation: 21
I had the same problem and this is what I discovered. The issue was not in my wire up code, it was actually in my controller class. I had an attribute on my controller action that was requiring the user to be in certain roles. This is ultimately what caused my redirect loop.
If you have that, you need to get rid of it and find a different way of handling the roles.
This shows the offending code: Circled code is what I removed.
Here is the link to the article that caused me to have my "aha" moment:
https://www.blinkingcaret.com/2016/01/20/authorization-redirect-loops-asp-net-mvc/
Hope this helps someone, took me too long to figure this out.
Upvotes: 2