The ComponentSpace OpenID Connect for ASP.NET Core is a .NET class library that provides OpenID provider functionality.
You can use this functionality to easily enable your ASP.NET Core web applications to participate in OpenID Connect flows as the OpenID provider. Example applications with full source code are included.
For a walkthrough of the example projects, refer to the OpenID Connect for ASP.NET Core Examples Guide.
The ComponentSpace OpenID Connect for ASP.NET Core .NET class library is compatible with the following frameworks:
· .NET 6.0 and above
The class library may be used with the following project types:
· ASP.NET Core Web Application (.NET Core)
The OpenID Connect for ASP.NET Core examples include solution and project files for:
· Visual Studio 2022 and above
In the application’s Program class, the following occurs:
The OpenID Connect services are added.
This includes registering the OpenID configuration. OpenID configuration may be specified as JSON either in the application’s appsettings.json or in a separate JSON file (eg oidc.json).
// Add OpenID provider services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider") |
Alternatively, OpenID configuration may be stored in a
database or some other format and specified programmatically through the
configuration API.
For information on OpenID configuration, refer to the OpenID Connect for
ASP.NET Core Configuration Guide.
The IOpenIDProvider interface supports OpenID Connect when acting as the OpenID provider.
The IOpenIDProvider will be made available, through dependency injection, to the application’s controllers wishing to call the API.
public class OpenIDController : ControllerBase { private readonly IOpenIDProvider _openIDProvider;
public OpenIDController(IOpenIDProvider openIDProvider) { _openIDProvider = openIDProvider; } } |
The ConfigurationName property specifies the OpenID configuration to use.
Setting this property is only required for multi-tenancy support.
Multi-tenancy support refers to a single application accommodating multiple customers or tenants each of whom has their own separate configuration.
For most use cases, a single configuration will suffice, and this property shouldn’t be set.
If there are multiple configurations, this property must be set to the name of the applicable configuration prior to any other OpenID API calls.
Refer to the OpenID Connect for ASP.NET Core Configuration Guide for more information.
string? ConfigurationName { get; set; } |
The GetMetadataAsync method returns the OpenID provider’s metadata.
This is the OpenID provider metadata defined in the OpenID Connect Discovery specification available at https://openid.net/specs/openid-connect-discovery-1_0.html.
This method may be called to implement the “.well-known/openid-configuration” endpoint.
Task<IActionResult> GetMetadataAsync (); |
The GetKeysAsync method returns the OpenID provider’s public keys as a JSON Web Key Set document.
This method may be called to implement the “jwks_uri” endpoint.
Task<IActionResult> GetKeysAsync(); |
The ReceiveAuthnRequestAsync method receives an OpenID Connect authentication request from a client.
The received authentication request is returned.
Task<AuthenticationRequest> ReceiveAuthnRequestAsync(); |
The SendAuthnResponseAsync method sends an OpenID Connect authentication response to a client.
Task<IActionResult> SendAuthnResponseAsync(string subject, IEnumerable<Claim> claims, string? accessToken = null, string? refreshToken = null, DateTime? utcAccessTokenExpiresAt = null); |
subject
The subject to include in the authentication response.
claims
The claims to include in the authentication response.
accessToken
The access token to include in the authentication response.
refreshToken
The refresh token to include in the authentication response.
utcAccessTokenExpiresAt
The UTC time when the access token expires.
If an error occurs at the OpenID provider, an error authentication response may be returned to the client.
Task<IActionResult> SendAuthnResponseAsync(Exception exception); |
exception
The exception that occurred.
Task<IActionResult> SendAuthnErrorResponseAsync(string error, string? errorDescription = null, string? errorUri = null); |
error
The error. Refer to the OpenIDConstants.ErrorCodes for standard error codes.
errorDescription
The error description.
errorUri
The error URI.
The GetTokensAsync method returns the ID token as part of the authorization code flow.
Task<IActionResult> GetTokensAsync(GetRefreshTokenResultAsync? getRefreshTokenResult = null, GetClientCredentialsResultAsync? getClientCredentialsResult = null, GetUserCredentialsResultAsync? getUserCredentialsResult = null); |
getRefreshTokenResult
The delegate that returns the access token, its expiry, and the new refresh token for the given refresh token.
This is only required if supporting the refresh_token grant type.
public delegate Task<RefreshTokenResult> GetRefreshTokenResultAsync(string clientID, string refreshToken); |
getClientCredentialsResult
The delegate that returns the access token and its expiry.
This is only required if supporting the client_credentials grant type.
public delegate Task<ClientCredentialsResult> GetClientCredentialsResultAsync(string clientID, string? scope = null); |
getUserCredentialsResult
The delegate that returns the access token and its expiry.
This is only required if supporting the password grant type.
public delegate Task<UserCredentialsResult> GetUserCredentialsResultAsync(string clientID, string? userName = null, string? userPassword = null, string? scope = null); |
The GetUserInfoAsync method returns the user information.
Task<IActionResult> GetUserInfoAsync(); |
The ReceiveLogoutRequestAsync method receives an OpenID Connect logout request from a client.
The received logout request is returned.
Task<LogoutRequest> ReceiveLogoutRequestAsync(); |
The SendLogoutResponseAsync method sends an OpenID Connect logout response to a client.
Task<IActionResult> SendLogoutResponseAsync(); |
The GetStatusAsync method returns the OpenID session status.
The returned dictionary’s keys are the client ID. The dictionary’s values are the corresponding status for that client.
Task<IDictionary<string, IOpenIDStatus>> GetStatusAsync(); |
The CreateJwtAccessTokenAsync method creates a JWT access token.
Task<string> CreateJwtAccessTokenAsync(string clientID, string audience, string? subject, string? scope, IList<Claim>? claims = null, DateTime? utcJwtExpiresAt = null); |
clientID
The client ID.
audience
The audience to include in the JWT.
subject
The subject to include in the JWT.
claims
The claims to include in the JWT.
utcJwtExpiresAt
The UTC time when the JWT expires.
The RevokeAccessTokenAsync method removes the access token from the internal access token cache so it won’t be sent to the client.
Task RevokeAccessTokenAsync(string accessToken); |
accessToken
The access token.
The ClearSessionAsync method clears the internal state for the current session.
Task ClearSessionAsync(); |
If an error occurs an exception is thrown.
It’s recommended that all API calls be wrapped in a try/catch with the exception being processed as required.
Several interfaces are exposed to enable custom implementations.
However, for most use cases, it’s not expected this will be required.
The IServiceCollection extension method, AddOpenIDProvider, adds the various default implementations of the OpenID interfaces to the .NET default services container. This should be called in the application’s Program class.
Some, or all, of these implementations may be replaced by calling IServiceCollection Add methods.
For example, the following code makes use of the default implementations of the OpenID interfaces except it replaces the IOidcSessionStore with a custom implementation.
// Add OpenID provider services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider"));
// Add a custom OpenID session store. builder.Services.AddScoped<ISessionStore, CustomSessionStore>(); |
The following table specifies the interfaces, default implementations and lifetimes that must be defined to a third-party Inversion of Control container if the .NET default services container isn’t being used.
Interface |
Implementation |
Lifetime |
- |
IOptionsMonitor<CertificateValidationOptions> |
Transient |
- |
IOptionsMonitor<DistributedSessionStoreOptions> |
Transient |
- |
IOptionsMonitor<HttpPostFormOptions> |
Transient |
- |
IOptionsMonitor<OpenIDConfigurations> |
Transient |
IAccessTokenCache |
AccessTokenCache |
Transient |
IAuthCodeCache |
AuthCodeCache |
Transient |
ICache |
DistributedCache |
Transient |
ICertificateLoader |
CertificateLoader |
Transient |
ICertificateValidator |
CertificateValidator |
Transient |
IClientAuthenticator |
ClientAuthenticator |
Transient |
IConfigurationResolver |
ConfigurationResolver |
Scoped |
IDistributedCache |
MemoryDistributedCache |
Singleton |
IHttpContextAccessor |
HttpContextAccessor |
Transient |
IHttpGetTransport |
HttpGetTransport |
Transient |
IHttpPostForm |
HttpPostForm |
Transient |
IHttpPostTransport |
HttpPostTransport |
Transient |
IIDGenerator |
IDGenerator |
Transient |
ILicense |
License |
Transient |
ILoggerFactory |
LoggerFactory |
Transient |
IOpenIDProvider |
OpenIDProvider |
Transient |
IOpenIDStatus |
OpenIDStatus |
Transient |
ISessionStore |
DistributedSessionStore |
Scoped |
ITokenSecurity |
TokenSecurity |
Transient |
ITransport |
Transport |
Transient |
For most use cases, it’s not expected that custom implementations of the above interfaces will be required.
The ICache interface caches session state and tokens.
For most use cases, it’s not expected that custom implementations will be required.
A default implementation is included that caches to an in-memory implementation of the IDistributedCache interface.
This is suitable for single server deployments.
For web farm deployments, an IDistributedCache implementation such as the RedisCache or SqlServerCache should be specified.
For more information, refer to the OpenID Connect for ASP.NET Core Web Farm Guide.
The IAuthCodeCache interface caches authorization codes.
For most use cases, it’s not expected that custom implementations will be required.
A default implementation is included that caches to the ICache implementation.
The IAccessTokenCache interface caches access tokens.
For most use cases, it’s not expected that custom implementations will be required.
A default implementation is included that caches to the ICache implementation.
The ICertificateValidator interface validates X.509 certificates to ensure they haven’t expired or aren’t otherwise invalid.
Consideration must be given to the performance impact associated with certificate validation during OpenID operations.
Certificate validators may be chained together if multiple implementations are required.
A default certificate validator is available and options for it may be set.
For example, the following code in the Program class turns on certificate chain checking.
using ComponentSpace.OpenID.Certificates;
builder.Services.Configure<CertificateValidationOptions>(options => { options.EnableChainCheck = true; });
// Add OpenID Connect services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider")); |
Alternatively, the options may be specified through configuration.
For example, the following section in appsettings.json turns on certificate chain checking.
"CertificateValidation": { " EnableChainCheck ": true } |
The following code makes use of this configuration.
using ComponentSpace.OpenID.Certificates;
// Configure the certificate validation. builder.Services.Configure<CertificateValidationOptions>( Configuration.GetSection("CertificateValidation"));
// Add OpenID Connect services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider")); |
The following code turns off expired certificate checking.
using ComponentSpace.OpenID.Certificates;
builder.Services.Configure<CertificateValidationOptions>(options => { options.EnableNotAfterCheck= false; });
// Add OpenID Connect services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider")); |
The IConfigurationResolver interface resolves OpenID configuration.
For most use cases, it’s not expected that custom implementations will be required.
A default implementation is included that resolves configuration through the configuration specified either using a configuration file (eg. appsettings.json) or programmatically.
Refer to the OpenID Connect for ASP.NET Core Configuration Guide for more details.
The IHttpPostForm interface creates the HTML form that's used when sending messages via HTTP Post.
A default implementation is included.
For most use cases, it’s not expected that custom implementations will be required.
A default implementation is included which uses the following HTML template.
The default HTML template may be replaced through the HttpPostFormOptions.FormTemplate property.
<html> <body> <noscript> <p> Since your browser doesn't support JavaScript, you must press the Continue button to proceed. </p> </noscript> {displayMessage} <form id=""openidform"" action=""{url}"" method=""post"" target=""{target}""> <div> {hiddenFormVariables} </div> <noscript> <div> <input type=""submit"" value=""Continue""/> </div> </noscript> </form> </body> {javaScript} </html> |
The following substitution parameters are supported.
displayMessage [optional]
The {displayMessage} is displayed in the browser while the HTML form is being posted.
url
The {url} is the action URL for the HTTP Post.
target
The {target} is the target URL for the HTTP Post (i.e. _self, _blank, _parent or _top).
hiddenFormVariables
The {hiddenFormVariables} are the hidden form inputs containing the information to be posted.
javaScript
The {javaScript} is the inline JavaScript used to automatically submit the HTML form.
The default inline JavaScript may be replaced through the HttpPostFormOptions.JavaScript property.
<script> function submitForm() { document.forms.openidform.submit(); }
if (document.readyState === ""loading"") { document.addEventListener(""DOMContentLoaded"", submitForm); } else { submitForm(); } </script> |
Options associated with the HTTP Post form may be set.
For example, the following code changes the target to a new tab in the browser.
using ComponentSpace.OpenID.Transport.Post;
builder.Services.Configure<HttpPostFormOptions>(options => { options.Target = "_blank"; });
// Add OpenID Connect services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider")); |
Alternatively, the options may be specified through configuration.
For example, the following section in appsettings.json specifies the browser target.
"HttpPostForm": { "Target": "_blank" } |
The following code makes use of this configuration.
using ComponentSpace.OpenID.Transport.Post;
// Configure the HTTP Post Form. builder.Services.Configure<HttpPostFormOptions>(Configuration.GetSection("HttpPostForm"));
// Add OpenID Connect services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider")); |
Content Security Policy (CSP) permits the control of resources, including JavaScripts, that the browser may load. It helps detect and protect against Cross Site Scripting (XSS) and other forms of attack.
CSP is specified through a Content-Security-Policy header sent to the browser. This also may be achieved through an equivalent <meta> element.
As the HTML form used to support HTTP-Post includes JavaScript, the CSP, if specified, must enable its loading.
A policy allowing all inline script to load is possible but not recommended.
Content-Security-Policy: script-src 'unsafe-inline' |
A nonce may be added to the JavaScript to identify it and permit its loading through policy.
<script nonce="2BAC238EBCE24A24ABCC11132361D228"> function submitForm() { document.forms.openidform.submit(); }
if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", submitForm); } else { submitForm(); } </script> |
The corresponding policy would include the nonce.
Content-Security-Policy: script-src 'nonce-2BAC238EBCE24A24ABCC11132361D228' |
A nonce may be included by specifying the HttpPostFormOptions.ContentSecurityPolicy.
using ComponentSpace.OpenID.Transport.Post;
// When using HTTP-Post, include a Nonce Content-Security-Policy header. builder.Services.Configure<HttpPostFormOptions>(options => { options.ContentSecurityPolicy = HttpPostFormOptions.ContentSecurityPolicyOption.Nonce; });
// Add OpenID Connect services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider")); |
A hash may be used to identify the JavaScript and permit its loading through policy.
The corresponding policy would include the hash.
Content-Security-Policy: script-src 'sha256- oJqv2rhhrRCF1O504qOiwpGkD/R3s5/Btx1EFtIkfcU=' |
A hash may be included by specifying the HttpPostFormOptions.ContentSecurityPolicy.
using ComponentSpace.OpenID.Transport.Post;
// When using HTTP-Post, include a Hash Content-Security-Policy header. builder.Services.Configure<HttpPostFormOptions>(options => { options.ContentSecurityPolicy = HttpPostFormOptions.ContentSecurityPolicyOption.Hash; });
// Add OpenID Connect services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider")); |
Rather than using inline script, a separate script file may be downloaded from a trusted site.
Typically, this will be the application site.
The script file contains the following JavaScript.
function submitForm() { document.forms.openidform.submit(); }
if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", submitForm); } else { submitForm(); } |
The corresponding policy would include self (i.e. the origin site) as a trusted source.
Content-Security-Policy: script-src 'self' |
Self may be included by specifying the HttpPostFormOptions.ContentSecurityPolicy and JavaScript source path.
using ComponentSpace.OpenID.Transport.Post;
// When using HTTP-Post, include a Self Content-Security-Policy header // and use a JavaScript file rather than inline JavaScript. builder.Services.Configure<HttpPostFormOptions>(options => { options.ContentSecurityPolicy = HttpPostFormOptions.ContentSecurityPolicyOption.Self; options.JavaScript = "<script src=\"/js/openid.js\"></script>"; });
// Add OpenID Connect services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider")); |
The ISessionStore interface supports storing OpenID session data.
For most use cases, it’s not expected that custom implementations will be required.
The default implementation stores OpenID session data in an ICache.
The key to the cache for individual session data is kept in an OpenID-specific HTTP cookie.
By default, the cookie’s name is “openid-session” and it’s marked as secure, samesite=none and HTTP only.
An example set-cookie response header is shown below.
set-cookie: openid-session=74225c85-20c5-4535-a289-4b173ff23e4a; path=/; secure; samesite=none; httponly |
The OpenID session cookie is created with a SameSite value of None.
However, if the MinimumSameSitePolicy for the application is set to SameSiteMode.Lax or SameSiteMode.Strict, the OIDC session cookie will take on this minimum setting. This will mean the browser won’t return the cookie and the corresponding OpenID session state cannot be identified.
To circumvent these issues, the recommended approach is to set MinimumSameSitePolicy to SameSiteMode.None and to specify the SameSite setting on individual cookies as required.
builder.Services.Configure<CookiePolicyOptions>(options => { options.MinimumSameSitePolicy = SameSiteMode.None; }); |
Options associated with the session store may be set.
For example, the following code specifies the name of the OpenID cookie.
using ComponentSpace.OpenID.Session;
builder.Services.Configure<SessionStoreOptions>(options => { options.CookieName = "my-openid-session"; });
// Add OpenID Connect services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider")); |
Alternatively, the options may be specified through configuration.
For example, the following section in appsettings.json specifies the name of the OpenID cookie.
"SessionStore": { "CookieName": "my-openid-session" } |
The following code makes use of this configuration.
using ComponentSpace.OpenID.Session;
// Configure the OpenID session store. builder.Services.Configure<SessionStoreOptions>(Configuration.GetSection("SessionStore"));
// Add OpenID Connect services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider")); |
The cookie’s domain defaults to the host name. The following example sets the session cookie’s domain. This is necessary if OpenID Connect occurs across subdomains.
using ComponentSpace.OpenID.Session;
builder.Services.Configure<SessionStoreOptions>(options => { options.CookieOptions.Domain = "componentspace.com"; });
// Add OpenID Connect services. builder.Services.AddOpenIDProvider(builder.Configuration.GetSection("OpenIDProvider")); |