Skip to content

Latest commit

 

History

History
518 lines (396 loc) · 21.9 KB

File metadata and controls

518 lines (396 loc) · 21.9 KB

Support

Installing the package

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

Usage guide

These examples will help you to understand how to use this library. You can also check out our ASP.NET Core samples.

Simplified Configuration (Recommended)

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 Scope property in configuration should be a space-separated string (e.g., "openid profile email"), which will be parsed into a list automatically.

Basic configuration

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();
}

That's it!

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.

Proxy configuration

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 BackchannelHttpClientHandler is provided.

Configure your own HttpMessageHandler implementation

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;
        }
    }
}

Configuration for cloud services or load balancers

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.

Self-Hosted login configuration

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.

Specifying the login_hint parameter

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");
}

Specifying the acr_values, enroll_amr_values and prompt parameters

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 prompt is equals to enroll_authenticator you have to indicate the URL that Okta should send callback to after the user app sends the enrollment request. The redirect_uri cannot be the same as the normal OIDC flow /authorization_code/callback since 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");
}

Login with an external identity provider

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.

Accessing OIDC Tokens

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.

Hooking into OIDC events

In the event a failure occurs, the Okta.AspNetCore library exposes OpenIdConnectEvents so you can hook into specific events during the authentication process.

Customizing claims

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.

Handling failures

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 OnAuthenticationFailed or OnRemoteFailure.

Configuration Reference

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.