## Documentation

- About
- Read First
  - What is IdentityServer
  - Installation Scenarios
- Migrations
- Bootstrapping
- Configuration & Integration
  - AdminUI Settings
  - Configuring AdminUI
  - Configuring AdminUI (NuGet)
  - Integrating with Identity Server
  - Integrating with Identity Server Custom Schema
  - Integrating with Quickstarts
  - Enabling Logging
- Installation
  - Installer
  - IIS
  - Azure
    - Install in Azure
    - Create Azure Web App
  - Docker
  - NuGet
- Custom Database Connections
- Custom Identity Store
  - Overview
  - Partial Implementation
  - Full Implementation
  - Tenancy With Entity Framework
  - NoSql Implementation (MongoDB)
- Users
  - Searching for Users
  - Adding Users
  - Editing Users
  - Deleting Users
  - Merge Users
  - Set User Password
  - Session Management
  - User Reports
- Roles
  - Creating Roles
  - Managing Roles
  - Managing Users in a Role
- Claim Types
- Clients
  - Searching for Clients
  - Adding Clients
  - Adding a SAML Client
  - Adding a WS-Fed Client
  - Editing Clients
  - Deleting Clients
  - Clone Client
- Import and Export
  - Exporting Clients
  - Importing Clients
  - Importing Client Configuration with Secrets
  - Importing SAML Clients with Certificates
  - Import and Export Via the API
- Dynamic Authentication Providers
  - What is Dynamic Authentication
  - Enabling or disabling Dynamic Authentication
  - Add a Dynamic Provider
    - First Steps
    - Adding OIDC Provider
    - Adding SAML Provider
  - Edit a Dynamic Provider
    - Accessing edit view
    - Editing a OIDC Provider
    - Editing a SAML Provider
  - Deleting a Provider
  - Searching for Provider
- Resources
  - Searching for Resources
  - Creating Resources
  - Editing resources
- Webhooks
  - Webhooks
  - Endpoint Configuration
- User Settings Endpoints
- Access Policy
- Auditing
- FAQs
- Troubleshooting
  - Unable to load AdminUI web page
  - Unable to contact IdentityServer
  - Unable to contact API
  - Unable to log in

### Partial Implementation

- -Using AdminUIs base stores
- -Extending Functionality
- -Creating a new class
- -Overriding certain calls

## Using AdminUIs base stores

Samples of the below code can be found on our GitHub

For this part of the documentation the code in Base Installation project will be considered the "Before".

### Extending Functionality

The example functionality extension will be performed on the `CreateUser` method on the `ISSOUserStore`.

For this, you must:

1. Create a class that derives from `SSOUserStore`
2. Create a factory that creates this class
3. Overwrite the `CreateUser` method and include a notifier

### Creating a new class

- To start, create a new class in your project call `CustomUserStore.cs`
- Make your class extend the base class `SSOUserStore`
- Create a constructor that calls `base` with the required interfaces
  - Because the AdminUI DI container has already registered the services needed to extend `SSOUserStore` you don't need to do any DI configuration on your side, you only need to import the correct interfaces.

After these steps you will end up with a class that looks like the following:

    public class CustomUserStore : SSOUserStore
    {
        public CustomUserStore(IIdentityUnitOfWorkFactory factory, ILookupNormalizer normaliser, IUserQueryFactory userQueryFactory) : base(factory, normaliser, userQueryFactory)
        {
        }
    }

To use this class instead of using the default implementation, you now need to provide an implementation of the `ISSOStoreFactory` that returns this implementation of the `ISSOUserStore` and not AdminUI's

- In a new file create a class called `CustomIdentityFactory` that implements `ISSOStoreFactory`
- In the constructor, pass in classes needed to create the new `CustomUserStore` and `CustomIdentityFactory`
- Override the `CreateUserStore` method, returning the `CustomUserStore`

After these steps you will have a new class that looks like this:

    public class CustomStoreFactory : ISSOStoreFactory
    {
        private readonly IIdentityUnitOfWorkFactory _factory;
        private readonly ILookupNormalizer _normaliser;
        private readonly IUserQueryFactory _userQueryFactory;

        public CustomStoreFactory(IIdentityUnitOfWorkFactory factory, ILookupNormalizer normaliser, IUserQueryFactory userQueryFactory)
        {
            _factory = factory ?? throw new ArgumentNullException(nameof(factory));
            _normaliser = normaliser ?? throw new ArgumentNullException(nameof(normaliser));
            _userQueryFactory = userQueryFactory ?? throw new ArgumentNullException(nameof(userQueryFactory));
        }

        public ISSOUserStore CreateUserStore()
        {
            return new CustomUserStore(_factory, _normaliser, _userQueryFactory);
        }

        public ISSORoleStore CreateRoleStore()
        {
            return new SSORoleStore(_factory, _normaliser, _userQueryFactory);
        }

        public ISSOClaimTypeStore CreateClaimTypeStore()
        {
            return new SSOClaimTypeStore(_factory, _normaliser);
        }
    }

Now register this factory during pipeline initialisation. In your `Program.cs` or `Startup.cs`, update the `UseAdminUI` call by adding `WithIdentityStore`. This method accepts a type that needs to implement `ISSOStoreFactory`

    var builder = WebApplication.CreateBuilder(args);

    builder.Services
            .AddAdminUI()
            .WithIdentityStore<CustomStoreFactory>();

If you run the code and put a breakpoint in your `CustomUserStore` constructor, you can see that you are now using your own code. Now you can extend it with your own functionality

Now: - Create a `NotificationService` interface that has a `Dispatch` method, create a class that inherits it and register it in the DI container - Pass it into `CustomUserStore`. You will also need to include it in the `CustomStoreFactory` constructor. - Overwrite the `CreateUser` method to use the notification service.

Now, when AdminUI creates a user, it will also call off to the new `NotificationService`.

    public class CustomUserStore : SSOUserStore
    {
        private readonly INotificationService _notificationService;

        public CustomUserStore(INotificationService notificationService, IIdentityUnitOfWorkFactory factory, ILookupNormalizer normaliser, IUserQueryFactory userQueryFactory) : base(factory, normaliser, userQueryFactory)
        {
            _notificationService = notificationService ?? throw new ArgumentNullException(nameof(notificationService));
        }

        public override Task<ISSOUser> CreateUser(ISSOUser user)
        {
            var createdUser = base.CreateUser(user);

            _notificationService.Dispatch("CreateUser");

            return createdUser;
        }
    }

The finished code can be found here

### Overriding certain calls

In this section you will create a stub service to show how you overwrite certain methods if you need your own logic surrounding a method.

A good example of this is the Delete method in the `ISSOUserStore`. By default, AdminUI performs a 'soft' deletion when deleting a user, wherein the `IsDeleted` flag is set to true. However, there is functionality within the `UserManager` used by AdminUI to perform a hard deletion wherein a user is completely removed from the database.

If you preferred a user to be completely removed during deletion:

- Create a class that inherits from `SSOUserStore` like above
- Overwrite `Delete` method
- Initialise an instance of the unit of work
- Call the `HardDelete` method on the `UserManager`

    public class CustomUserStore : SSOUserStore
    {
        private readonly INotificationService _notificationService;
        private readonly IIdentityUnitOfWorkFactory _factory;

        public CustomUserStore(INotificationService notificationService, IIdentityUnitOfWorkFactory factory, ILookupNormalizer normaliser, IUserQueryFactory userQueryFactory) : base(factory, normaliser, userQueryFactory)
        {
            _notificationService = notificationService ?? throw new ArgumentNullException(nameof(notificationService));
            _factory = factory ?? throw new ArgumentNullException(nameof(factory));
        }

        ...

        public override async Task DeleteUser(ISSOUser user)
        {
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            using var uow = _factory.Create();
            var databaseUser = user.ToIdentityExpressUser();
            await uow.UserManager.HardDeleteAsync(databaseUser);
        }
    }

As a result, when AdminUI's service layer calls the `Delete` method on the user store, instead of the default delete functionality, your new functionality will be called and the user will be completely removed from the database.

Previous Next