CI.Optimizely.CustomerEngagementPlatform.Security 0.9.10-beta

This is a prerelease version of CI.Optimizely.CustomerEngagementPlatform.Security.
The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package CI.Optimizely.CustomerEngagementPlatform.Security --version 0.9.10-beta
                    
NuGet\Install-Package CI.Optimizely.CustomerEngagementPlatform.Security -Version 0.9.10-beta
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CI.Optimizely.CustomerEngagementPlatform.Security" Version="0.9.10-beta" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CI.Optimizely.CustomerEngagementPlatform.Security" Version="0.9.10-beta" />
                    
Directory.Packages.props
<PackageReference Include="CI.Optimizely.CustomerEngagementPlatform.Security" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CI.Optimizely.CustomerEngagementPlatform.Security --version 0.9.10-beta
                    
#r "nuget: CI.Optimizely.CustomerEngagementPlatform.Security, 0.9.10-beta"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CI.Optimizely.CustomerEngagementPlatform.Security@0.9.10-beta
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CI.Optimizely.CustomerEngagementPlatform.Security&version=0.9.10-beta&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=CI.Optimizely.CustomerEngagementPlatform.Security&version=0.9.10-beta&prerelease
                    
Install as a Cake Tool

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 Password field 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 IServiceCollection and IApplicationBuilder to 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:

  1. User accesses a protected resource.
  2. If the user is not authenticated, they are redirected/routed to the custom login page. At where the user can choose their login method.
  3. User logs in using one of the available methods (e.g., username/password, Microsoft, Google).
  4. 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 the ClaimsIdentity.
  5. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.