You can install the package by using the NuGet Package Explorer to search for Okta.AspNetCore.
Or, you can use the dotnet command:
dotnet add package Okta.AspNetCore
These examples will help you to understand how to use this library. You can also check out our ASP.NET Core samples.
Starting in version 5.0.0, you can use the simplified IConfiguration binding to automatically load all Okta settings from your configuration:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OktaDefaults.MvcAuthenticationScheme;
})
.AddCookie()
.AddOktaMvc(Configuration); // All options bound automatically from "Okta" section
services.AddMvc();
}Add the Okta section to your appsettings.json:
{
"Okta": {
"OktaDomain": "https://dev-123456.okta.com",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret",
"AuthorizationServerId": "default",
"PostLogoutRedirectUri": "https://localhost:5001/",
"Scope": "openid profile email"
}
}Alternative: Using Issuer URL
You can also use the full Issuer URL instead of specifying OktaDomain and AuthorizationServerId separately:
{
"Okta": {
"Issuer": "https://dev-123456.okta.com/oauth2/default",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret",
"PostLogoutRedirectUri": "https://localhost:5001/",
"Scope": "openid profile email"
}
}The Issuer URL will be automatically parsed to extract the OktaDomain and AuthorizationServerId.
You can also specify a custom configuration section name:
.AddOktaMvc(Configuration, "MyOktaSettings");Note: The
Scopeproperty in configuration should be a space-separated string (e.g.,"openid profile email"), which will be parsed into a list automatically.
If you prefer explicit control, you can still use the traditional manual configuration approach:
public void ConfigureServices(IServiceCollection services)
{
var oktaMvcOptions = new OktaMvcOptions()
{
OktaDomain = Configuration.GetSection("Okta").GetValue<string>("OktaDomain"),
ClientId = Configuration.GetSection("Okta").GetValue<string>("ClientId"),
ClientSecret = Configuration.GetSection("Okta").GetValue<string>("ClientSecret"),
Scope = new List<string> { "openid", "profile", "email" },
};
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OktaDefaults.MvcAuthenticationScheme;
})
.AddCookie()
.AddOktaMvc(oktaMvcOptions);
services.AddMvc();
}Note: Starting in v4.2.0 you can now configure the authentication scheme:
.AddOktaMvc("myScheme", oktaMvcOptions);.
ASP.NET Core Identity framework uses "Identity.Application" authentication scheme. Here is how configuration code looks like in such case:
public void ConfigureServices(IServiceCollection services)
{
var oktaMvcOptions = new OktaMvcOptions()
{
OktaDomain = Configuration.GetSection("Okta").GetValue<string>("OktaDomain"),
ClientId = Configuration.GetSection("Okta").GetValue<string>("ClientId"),
ClientSecret = Configuration.GetSection("Okta").GetValue<string>("ClientSecret"),
Scope = new List<string> { "openid", "profile", "email" },
};
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
})
.AddCookie()
.AddOktaMvc(oktaMvcOptions);
services.AddMvc();
}Placing the [Authorize] attribute on your controllers or actions will check whether the user is logged in, and redirect them to Okta if necessary.
ASP.NET automatically populates HttpContext.User with the information Okta sends back about the user. You can check whether the user is logged in with User.Identity.IsAuthenticated in your actions or views.
If your application requires proxy server settings, specify the Proxy property on OktaMvcOptions.
public void ConfigureServices(IServiceCollection services)
{
var oktaMvcOptions = new OktaMvcOptions()
{
OktaDomain = Configuration.GetSection("Okta").GetValue<string>("OktaDomain"),
ClientId = Configuration.GetSection("Okta").GetValue<string>("ClientId"),
ClientSecret = Configuration.GetSection("Okta").GetValue<string>("ClientSecret"),
Scope = new List<string> { "openid", "profile", "email" },
Proxy = new ProxyConfiguration
{
Host = "http://{yourProxyHostNameOrIp}",
Port = 3128, // Replace this value with the port that your proxy server listens on
Username = "{yourProxyServerUserName}",
Password = "{yourProxyServerPassword}",
}
};
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OktaDefaults.MvcAuthenticationScheme;
})
.AddCookie()
.AddOktaMvc(oktaMvcOptions);
services.AddMvc();
}Note: The proxy configuration is ignored when a
BackchannelHttpClientHandleris provided.
Starting in Okta.AspNet 2.0.0/Okta.AspNetCore 4.0.0, you can now provide your own HttpMessageHandler implementation to be used by the uderlying OIDC middleware. This is useful if you want to log all the requests and responses to diagnose problems, or retry failed requests among other use cases. The following example shows how to provide your own logging logic via Http handlers:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseOktaMvc(new OktaMvcOptions
{
BackchannelHttpClientHandler = new MyLoggingHandler((logger),
});
}
}
public class MyLoggingHandler : DelegatingHandler
{
private readonly ILogger _logger;
public MyLoggingHandler(ILogger logger)
{
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
_logger.Trace($"Request: {request}");
try
{
var response = await base.SendAsync(request, cancellationToken);
_logger.Trace($"Response: {response}");
return response;
}
catch (Exception ex)
{
_logger.Error($"Something went wrong: {ex}");
throw;
}
}
}If you plan to deploy your application to a cloud service, you may need to do additional customization. Cloud services usually create similar environments in which applications run inside a Docker container using HTTP and behind a reverse proxy or load balancer. Cloud environment may comprise API gateway, firewall, load balancer, reverse proxy. This environment may be initially configured to use HTTP for incoming requests.
Although cloud services configurations may vary, general rules are:
- Make sure the environment is using HTTPS for incoming requests.
This may be the default setting or may require some amount of fiddling as in case of AWS.
- Application should be configured to consider forwarded headers information.
Backend application typically listens on HTTP and it sits behind a reverse proxy or load balancer that accepts user requests over HTTPS. If this isn't taken into account redirect_uri authentication parameter will not be built correctly as the function used for that Microsoft.AspNetCore.Authentication.BuildRedirectUri relies on request scheme.
For information on how to configure your application in a proxied, load balanced environment, see Configure ASP.NET Core to work with proxy servers and load balancers.
Azure environment with Front Door load balancer should work properly after forwarded headers configuration is added like so:
Startup.ConfigureServices:
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto;
});Use code similar to the following to properly configure a load balanced AWS or Google Cloud with App Engine Flex environment.
Startup.Configure:
app.Use((context, next) =>
{
var xProtoHeaders = context.Request.Headers["X-Forwarded-Proto"];
if (xProtoHeaders.Count > 0 && xProtoHeaders[0].ToLower() == "https")
context.Request.Scheme = "https";
return next();
});Don't use app.UseHttpsRedirection(); as it may cause a client infinite redirection loop.
- Do additional configuration if needed.
Because of Docker specifics on some cloud services authentication may fail with the message Unable to unprotect the message.State. This most likely means you need to configure Data Protection storage. For Google Cloud, see ASP.Net Core data protection provider.
public void ConfigureServices(IServiceCollection services)
{
var oktaMvcOptions = new OktaMvcOptions()
{
OktaDomain = Configuration.GetSection("Okta").GetValue<string>("OktaDomain"),
ClientId = Configuration.GetSection("Okta").GetValue<string>("ClientId"),
ClientSecret = Configuration.GetSection("Okta").GetValue<string>("ClientSecret"),
Scope = new List<string> { "openid", "profile", "email" },
};
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = new PathString("/Account/SignIn");
})
.AddOktaMvc(oktaMvcOptions);
services.AddMvc();
}Note: If you are using role-based authorization and you need to redirect unauthorized users to an access-denied page or similar, check out CookieAuthenticationOptions.AccessDeniedPath.
The login_hint parameter allows you to pass a username to prepopulate when prompting for authentication. For more details check out the Okta documentation.
Add the following action in your controller:
public IActionResult SignIn()
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
var properties = new AuthenticationProperties();
properties.Items.Add(OktaParams.LoginHint, "[email protected]");
properties.RedirectUri = "/Home/";
return Challenge(properties, OktaDefaults.MvcAuthenticationScheme);
}
return RedirectToAction("Index", "Home");
}| Parameter | Description | Required? |
|---|---|---|
acr_values |
When included in the authentication request, increases the level of user assurance. | No |
prompt |
Indicate the pipeline the intent of the request, such as, support enrollment of a new factor. | No |
enroll_amr_values |
A space-delimited, case-sensitive string that represents a list of authenticator method references. | No |
Note: When
promptis equals toenroll_authenticatoryou have to indicate the URL that Okta should send callback to after the user app sends the enrollment request. Theredirect_uricannot be the same as the normal OIDC flow/authorization_code/callbacksince there's no code involved in this flow. Instead, you have to specify a callback URI (which has to be added to your application's allowed URLs in your Okta dashboard) in your application where, for example, you can process the response and redirect accordingly. Also, it's expected you already have a session before triggering the authenticator enrollment flow.
For more details see the Okta documentation.
public IActionResult SignIn()
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
var properties = new AuthenticationProperties();
properties.Items.Add(OktaParams.AcrValues, "urn:okta:loa:1fa:pwd");
properties.Dictionary.Add(OktaParams.Prompt, "enroll_authenticator");
properties.Dictionary.Add(OktaParams.EnrollAmrValues, "sms okta_verify");
properties.RedirectUri = "https://localhost:44314/Account/EnrollCallback";
return Challenge(properties, OktaDefaults.MvcAuthenticationScheme);
}
return RedirectToAction("Index", "Home");
}
public IActionResult Callback()
{
//...
// If enrollment was successful
return RedirectToAction("Index", "Home");
}Add the following action in your controller:
public IActionResult SignInWithIdp(string idp)
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
var properties = new AuthenticationProperties();
properties.Items.Add(OktaParams.Idp, idp);
properties.RedirectUri = "/Home/";
return Challenge(properties, OktaDefaults.MvcAuthenticationScheme);
}
return RedirectToAction("Index", "Home");
}The Okta.AspNetCore library includes your identity provider id in the authorize URL and the user is prompted with the identity provider login. For more information, check out our guides to add an external identity provider.
To access OIDC tokens, AspNet Core provides the HttpContext.GetTokenAsync(...) extension method. The following is an example of how to access OIDC tokens from your HomeController:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
public class TokenModel
{
public string Name { get; set; }
public string Value { get; set; }
}
public class HomeController : Controller
{
[Authorize]
public async Task<ActionResult> OIDCToken(string tokenName)
{
var tokenValue = await HttpContext.GetTokenAsync(tokenName);
return View(new TokenModel { Name = tokenName, Value = tokenValue });
}
}This example assumes you have a view called OIDCToken whose model is of type TokenModel. The OIDC tokens are id_token and access_token as well as refresh_token if available.
In the event a failure occurs, the Okta.AspNetCore library exposes OpenIdConnectEvents so you can hook into specific events during the authentication process.
The following is an example of how to use events to add custom claims to the token:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddOktaMvc(new OktaMvcOptions
{
// ... other configuration options removed for brevity ...
OpenIdConnectEvents = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
ClaimsIdentity claimsIdentity = context.Principal.Identity as ClaimsIdentity;
claimsIdentity.AddClaim(new Claim("MyCustomClaimKey", "MyCustomClaimValue"));
return Task.CompletedTask;
}
},
});
}Note: For more information See
OnTokenValidated.
The following is an example of how to use events to handle failures:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddOktaMvc(new OktaMvcOptions
{
// ... other configuration options removed for brevity ...
OpenIdConnectEvents = new OpenIdConnectEvents
{
OnAuthenticationFailed = OnAuthenticationFailed,
OnRemoteFailure = OnOktaApiFailure,
},
});
}
public async Task OnOktaApiFailure(RemoteFailureContext context)
{
await Task.Run(() =>
{
context.Response.Redirect("{YOUR-EXCEPTION-HANDLING-ENDPOINT}?message=" + context.Failure.Message);
context.HandleResponse();
});
}
public async Task OnAuthenticationFailed(AuthenticationFailedContext context)
{
await Task.Run(() =>
{
context.Response.Redirect("{YOUR-EXCEPTION-HANDLING-ENDPOINT}?message=" + context.Exception.Message);
context.HandleResponse();
});
}
}Note: For more information See
OnAuthenticationFailedorOnRemoteFailure.
The OktaMvcOptions class configures the Okta middleware. You can see all the available options in the table below:
| Property | Required? | Details |
|---|---|---|
| OktaDomain | Yes | Your Okta domain, i.e https://dev-123456.oktapreview.com |
| ClientId | Yes | The client ID of your Okta Application |
| ClientSecret | Yes | The client secret of your Okta Application |
| CallbackPath | Yes | The location Okta should redirect to process a login. This is typically http://{yourApp}/authorization-code/callback. No matter the value, the redirect is handled automatically by this package, so you don't need to write any custom code to handle this route. |
| AuthorizationServerId | No | The Okta Authorization Server to use. The default value is default. Use string.Empty if you are using the Org Authorization Server. |
| PostLogoutRedirectUri | No | The location Okta should redirect to after logout. If blank, Okta will redirect to the Okta login page. |
| Scope | No | The OAuth 2.0/OpenID Connect scopes to request when logging in. The default value is openid profile. |
| GetClaimsFromUserInfoEndpoint | No | Whether to retrieve additional claims from the UserInfo endpoint after login. The default value is true. |
| ClockSkew | No | The clock skew allowed when validating tokens. The default value is 2 minutes. |
| Proxy | No | An object describing proxy server configuration. Properties are Host, Port, Username and Password |
| OpenIdConnectEvents | No | Specifies the events which the underlying OpenIdConnectHandler invokes to enable developer control over the authentication process. |
| BackchannelTimeout | No | Timeout value in milliseconds for back channel communications with Okta. The default value is 1 minute. |
| BackchannelHttpClientHandler | No | The HttpMessageHandler used to communicate with Okta. |
You can store these values (except the events) in the appsettings.json, but be careful when checking in the client secret to the source control.
Note: You can use the The Org Authorization Server for common use cases such as adding authentication to your MVC Application or checking user's profile, but the access token issued by this Authorization Server cannot be used or validated by your own applications. Check out the Okta documentation to learn more.
