CI.Optimizely.CustomerEngagementPlatform.Security
0.9.10-beta
dotnet add package CI.Optimizely.CustomerEngagementPlatform.Security --version 0.9.10-beta
NuGet\Install-Package CI.Optimizely.CustomerEngagementPlatform.Security -Version 0.9.10-beta
<PackageReference Include="CI.Optimizely.CustomerEngagementPlatform.Security" Version="0.9.10-beta" />
<PackageVersion Include="CI.Optimizely.CustomerEngagementPlatform.Security" Version="0.9.10-beta" />
<PackageReference Include="CI.Optimizely.CustomerEngagementPlatform.Security" />
paket add CI.Optimizely.CustomerEngagementPlatform.Security --version 0.9.10-beta
#r "nuget: CI.Optimizely.CustomerEngagementPlatform.Security, 0.9.10-beta"
#:package CI.Optimizely.CustomerEngagementPlatform.Security@0.9.10-beta
#addin nuget:?package=CI.Optimizely.CustomerEngagementPlatform.Security&version=0.9.10-beta&prerelease
#tool nuget:?package=CI.Optimizely.CustomerEngagementPlatform.Security&version=0.9.10-beta&prerelease
Security and authentication extensions
Notice: This package is part of the Optimizely Customer Engagement Platform, Customer Implementation projects, and is not intended for use outside of that context.
Features
- Provides a simplified method for authentication and authorization for internal projects.
- Runs on top of ASP.NET framework
- Easy and intuitive interface to store user and session data by overriding
IUserIdentityStorage - Supports different login methods with minimal configuration efforts, including:
- User/Password combination
- Microsoft Account login (OAuth)
- Google Account login (OAuth2)
- Supports custom Login, Logout, and AccessDenied pages
- Supports JWT tokens for API authentication
Background
This package is not "Reinventing the wheel". This just wraps all required configuration logic and provide a simple interface to use it. This is believed to save time when applying authentication and authorization to internal projects - where security is required, but not the main focus of the project.
Components, classes, and interfaces
- UserIdentity stores user identity information. The
Passwordfield is optional if using OAuth. Otherwise, you have to implement your own encryption logic. - UserSession stores session information, including the user identity and session expiration. This is used to manage user sessions, JWT generation, and ensure they are valid.
- IUserIdentityStorage is an interface to store user identity information. You can implement your own storage logic, e.g., using a database or in-memory storage.
- AppAuthenticationOptions is a class that holds configuration options for the authentication system, such as login paths, logout paths, and access denied paths.
- ExternalAuthenticationOptions is a class that holds configuration options for external authentication providers like Microsoft and Google.
- Extension methods for
IServiceCollectionandIApplicationBuilderto register the authentication services and configure the options.
Usage
This package works with ASP.NET framework on Net Core 8.0 and later. The project can be either Razor Pages, Web API, or MVC based.
Identity Storage implementation
Implementing IUserIdentityStorage is required for this project. This can be done using a database, in-memory storage, or any other storage mechanism.
The implementation should handle user creation, retrieval, and validation.
Tips If your project only requires external authentication (Microsoft or Google), you can just implement with a minimum effort. Example: Just store users and sessions in an in-memory ConcurrentDictionary.
Configurations
Acquire configuration for your app:
- Microsoft Login: [https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app?tabs=client-secret#add-credentials]
- Google Login: [https://developers.google.com/identity/protocols/oauth2/web-server#creatingcred]
appSettings configuration:
{
// Basic configuration
"AppAuthentication": {
"JwtSecret": "...", // Use a randomized hex string with at least 256 characters
"LoginPath": "/authentication/login",
"LogoutPath": "/authentication/logout",
"AccessDeniedPath": "/authentication/denied",
"Exclusions": [
"/favicon.ico",
"/robots.txt",
"/logo.png",
"/authentication",
// List of paths to exclude from authentication (can be accessed without logging in)
]
},
// External authentication configuration
"ExternalAuthentication": {
"AllowedEmailDomains": [ "episerver.com", "optimizely.com" ], // List of allowed email domains for user registration
"AdminUsers": [ "giang.nguyen@optimizely.com" ], // Defines admin users by email address, any user with this email will be granted "*" role
"Google": {
"Enabled": true,
"ClientId": "",
"ClientSecret": ""
},
"Microsoft": {
// Same as above
}
}
}
In web app's initialization, register the authentication services and configure the options:
// var services = builder.Services;
services.AddMvc().AddSessionStateTempDataProvider();
services.AddSession();
services.AddSecurityServices(builder.Configuration, new InMemoryUserIdentityStorage());
services.AddExternalAuthentication(configuration);
// var app = builder.Build();
app.UseAuthorization();
app.UseAuthenticationMiddleware();
app.MapAuthenticationRedirects(); // or app.MapAuthenticationController(...); to map with custom controller/actions
app.UseExternalAuthentication(configuration);
Add authorize attribute to pages, controllers or actions that require authentication:
[Authorize(Role = "role1,role2")]
// or
[AppAuthentication("role1", "role2")]
Logic
When logged in, the user identity is stored in the session and can be accessed via HttpContext.User.
If registering users using external authentication, everyone will have the role Authorized by default.
High-level logic:
- User accesses a protected resource.
- If the user is not authenticated, they are redirected/routed to the custom login page. At where the user can choose their login method.
- User logs in using one of the available methods (e.g., username/password, Microsoft, Google).
- If the login is successful, the user identity is stored in the session,
HttpClient.User.Identity.Claims. A JWT token is also generated and stored in theClaimsIdentity. - If user's roles does not match at least one of the required roles, they are redirected to the access denied page. (Roles are case-insensitive)
Data transition:
UserIdentity (from storage or OAuth) -> UserSession (in-memory) -> HttpContext.User.Identity.Claims
Backing utility classes:
- HttpContextExtension: Provides extension methods to quickly authenticate, sign in, sign out using the current
HttpContext. - Jwt.LoginSession (internal): Main logic for processing the user sessions (creating, validating, renewing, etc.) using JWT tokens.
- Jwt.TokenProcessor (internal): Handles JWT token generation and validation.
Customization
Custom MVC controller for login/logout:
public class AuthenticationController : Controller
{
private readonly IUserIdentityStorage _identityStorage;
private readonly AppAuthenticationOptions _authenOptions;
private readonly ExternalAuthenticationOptions _extAuthenOptions;
public AuthenticationController(IUserIdentityStorage identityStorage, AppAuthenticationOptions authenOptions, ExternalAuthenticationOptions extAuthenOptions)
{
_identityStorage = identityStorage;
_authenOptions = authenOptions;
_extAuthenOptions = extAuthenOptions;
}
[HttpGet]
public IActionResult Login([FromQuery]string? redirectUrl = null)
{
// Check if the user has already logged in
var session = HttpContext.GetUserSession();
if (session != null)
{
// do something when user is already logged in
}
// Get available login methods:
var model = new {
BasicLoginEnabled: _authenOptions.PasswordAuthentication,
MicrosoftLoginEnabled: _extAuthenOptions.Microsoft.Enabled,
GoogleLoginEnabled: _extAuthenOptions.Google.Enabled,
RedirectUrl = redirectUrl ?? "/",
// Gets URLs that triggers external authentication challenges
// Add these URLs to the login buttons in your view (if enabled)
MicrosoftChallengeUrl = ExternalAuthenticationOptions.GetChallengeUrl(_extAuthenOptions.Microsoft.Name, redirectUrl),
GoogleChallengeUrl = ExternalAuthenticationOptions.GetChallengeUrl(_extAuthenOptions.Microsoft.Name, redirectUrl),
};
return View(model);
}
[HttpPost]
public async Task<IActionResult> PasswordLogin([FromForm]string user, [FromForm]string password, [FromForm]string? redirectUrl)
{
if (!_authenOptions.PasswordAuthentication) {
return BadRequest("Password authentication is not enabled.");
}
// Validate user credentials
var identity = await _identityStorage.AuthenticateUser(user.Trim().ToLowerInvariant(), pass);
if (identity == null)
{
ModelState.AddModelError("", "Invalid username or password.");
return View("Login");
}
// Validate user and create session
bool result = await HttpContext.AuthenticateSession(identity, createUserIfNotExist: false); // In this example, the logic does not support creating new users, only authenticating existing ones.
if (!result)
{
ModelState.AddModelError("", "Failed to authenticate user.");
return View("Login");
}
return RedirectToAction("Index", "Home");
}
[HttpGet]
public async Task<IActionResult> Logout()
{
HttpContext.SignOutSession();
// Custom logout logic
return RedirectToAction("Index", "Home");
}
public IActionResult AccessDenied([FromQuery]string? redirectUrl = null, [FromQuery]string? requiredRoles = null)
{
// Display which role is needed for access
// Can check current user's roles using HttpContext.GetUserSession()?.Identity.GetRoles()
return View();
}
}
API authorization using JWT:
// Retriving the JWT token in back-end
public string GetJwtToken()
{
var token = HttpContext.GetJwtToken();
if (token == null)
{
return "No JWT token available. Please log in first.";
};
return token;
}
// API endpoint
[Authorize]
[HttpGet("api/v1/getsomething")]
public async Task<IActionResult> GetSomething()
{
// Your logic goes here
return Ok(new { message = $"Hello, {HttpContext.GetUserSession().Identity.Name}!" });
}
const response = await fetch("https://example.org/api/v1/getsomething", {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + jwtToken, // Use the JWT token in the Authorization header
"X-Authentication-Token": jwtToken, // Another example: Use custom X-Authentication-Token header if Authorization is not suitable (i.e proxied requests)
},
// …
});
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.AspNetCore.Authentication.Google (>= 8.0.10)
- Microsoft.AspNetCore.Authentication.MicrosoftAccount (>= 8.0.10)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- System.IdentityModel.Tokens.Jwt (>= 8.10.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|
Minor bug fixes.