I have been playing around with Okta for a couple of days and so far very impressed. I have been struggling with something however…
I am building a .Net MVC App (crucially this is not a Web API project). I have been using the Sign In Widget which works well but I couldn’t find any documentation for how to get this to place nice with MVC/Owin authentication (the documentations seems to suggest more using the Okta sign in page but I need to customize the look and feel of the login to match the rest of the app).
My current work around is to redirect the Javascript sign in page to to the login method for the Okta sign in page (which seems to work!) but I’m fairly sure this is a silly round about way to achieve what I’m trying to do…
Happy to post more examples of code etc if any of this is unclear but hoping someone may have done this before or be able to point me in the direction of some tutorials on how to set this up.
The easiest way is to redirect to the Okta hosted sign-in page, as you mentioned, although you can’t fully customize the page (yet). We’re rolling out a feature soon that will allow you to totally customize the hosted sign-in page so you can make it look however you want.
In the meantime, a workaround is your best bet. We don’t have a tutorial for this right now, sorry. If you post some code snippets, I can take a look at what you have and offer suggestions.
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
ConfigureAuth(app);
}
private void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
});
var clientId = ConfigurationManager.AppSettings["okta:ClientId"].ToString();
var clientSecret = ConfigurationManager.AppSettings["okta:ClientSecret"].ToString();
var issuer = ConfigurationManager.AppSettings["okta:Issuer"].ToString();
var redirectUri = ConfigurationManager.AppSettings["okta:RedirectUri"].ToString();
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
ClientSecret = clientSecret,
Authority = issuer,
RedirectUri = redirectUri,
ResponseType = "code id_token",
UseTokenLifetime = false,
Scope = "openid profile",
PostLogoutRedirectUri = ConfigurationManager.AppSettings["okta:PostLogoutRedirectUri"].ToString(),
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = context =>
{
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idToken = context.OwinContext.Authentication.User.Claims.FirstOrDefault(c => c.Type == "id_token")?.Value;
context.ProtocolMessage.IdTokenHint = idToken;
}
return Task.FromResult(true);
},
AuthorizationCodeReceived = async context =>
{
// Exchange code for access and ID tokens
var tokenClient = new TokenClient(
issuer + "/v1/token", clientId, clientSecret);
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(context.ProtocolMessage.Code, redirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var userInfoClient = new UserInfoClient(issuer + "/v1/userinfo");
var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
var identity = new ClaimsIdentity();
identity.AddClaims(userInfoResponse.Claims);
identity.AddClaim(new Claim("id_token", tokenResponse.IdentityToken));
identity.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
{
identity.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
}
var nameClaim = new Claim(ClaimTypes.Name, userInfoResponse.Claims.FirstOrDefault(c => c.Type == "name")?.Value);
identity.AddClaim(nameClaim);
context.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(identity.Claims, context.AuthenticationTicket.Identity.AuthenticationType),
context.AuthenticationTicket.Properties);
}
}
});
}
}
This actually does work pretty well - it seems though Okta is reading the cookie set by the JS page and therefore doesn’t prompt for credentials and instead just creates me a session and logs me in. This is the exact behavior I was hoping to achieve however it didn’t seem like I was doing it the right way.
Long term formatting the Okta hosted login page would definitely be the cleanest way of doing this (although even then I would rather it appear under our domain as some of the data we deal with is very sensitive and clients may understand why they are on a “different website” to enter their credentials).
Absolutely - my existing method seems to work but my “concern” is I’m not that sure why! Will definitely switch it over as soon as that becomes an option
Can’t wait to see those options in an upcoming version!
Your approach looks fine to me. It works because the Okta hosted page checks whether the Okta cookie exists already (representing an active session), and if so, it doesn’t bother prompting the user again.
It will definitely be cleaner when the full customization features roll out, but you’re fine for now.