## 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

### NoSql Implementation (MongoDB)

- -Overview
- -MongoDB Configuration
- -Implementing the entity models
- -ISSOUser Implementation
- -ISSORole Implementation
- -ISSOClaimType Implementation
- -ISSOClaim Implementation
- -MongoDB Driver Class Mapping
- -Store Implementations
- -ISSOUserStore Implementation
- -ISSORoleStore Implementation
- -ISSOClaimTypeStore Implementation
- -ISSOStoreFactory Implementation
- -Summary

## Overview

If you have a requirement to use NoSQL database providers such as MongoDB to manage your users within IdentityServer, you can leverage AdminUI's Custom Identity Store interfaces to achieve this.

This document will outline the basic implementations needed to achieve this integration within MongoDB using the official `MongoDB.Driver` package within our solution.

## MongoDB Configuration

For our database, you will be using a locally hosting MongoDB database. To make things easier you can place MongoDB configuration in your `appsettings.json` file.

    "IdentityDatabase": {
        "ConnectionString": "mongodb://localhost:27017",
        "DatabaseName": "AdminUIUserStore",
        "UsersCollectionName": "Users",
        "RolesCollectionName": "Roles",
        "ClaimTypesCollectionName": "ClaimTypes"
      },

You can also create an object to deserialize these configuration values:

    public class IdentityStoreDatabaseSettings
    {
        public string ConnectionString { get; set; } = null!;

        public string DatabaseName { get; set; } = null!;

        public string UsersCollectionName { get; set; } = null!;

        public string RolesCollectionName { get; set; } = null!;

        public string ClaimTypesCollectionName { get; set; } = null!;
    }

To access this configuration object, add it to your service collection in the `Program.cs` class.

`builder.Services.Configure<IdentityStoreDatabaseSettings>(builder.Configuration.GetSection("IdentityDatabase"));`

## Implementing the entity models

The first step to achieving NoSQL integration with AdminUI is to create concrete implementations of our new model interfaces found within the `Rsk.CustomIdentity` NuGet package.

These interfaces are as follows:

- ISSOUser
- ISSORole
- ISSOClaim
- ISSOClaimType

## ISSOUser Implementation

First, create an implementation of the `ISSOUser` interface.

    public class CustomSSOUser : ISSOUser
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
        public string Email { get; set; }
        public string ConcurrencyStamp { get; set; }
        public bool TwoFactorEnabled { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public bool IsBlocked { get; }
        public bool IsDeleted { get; set; }
        public bool LockoutEnabled { get; set; }
        public DateTimeOffset? LockoutEnd { get; }

        [BsonIgnoreIfNull]
        public ICollection<ISSOClaim> Claims { get; set; } = new List<ISSOClaim>();

        //Not part of the ISSOUser interface. Included to make querying user roles easier.
        [BsonIgnoreIfNull]
        public ICollection<ISSORole> Roles { get; set; } = new List<ISSORole>();
    }

The fields contained within this interface are the foundations of a user within AdminUI. If you have any additional fields you wish to manage within AdminUI, you'll need to translate them to claims and place them in the `Claims` collection on the user class.

There are a few MongoDB specific attributes on our `CustomSSOUser` class. Firstly, the `[BsonId]` attribute specifies the field to be used as the primary key for the object.

Below the `[BsonId]` attribute, we have the `[BsonRepresentation(BsonType.ObjectId)]` attribute. This attribute is used to specify that the Id field on our object is to be seen (or represented) within MongoDB as an `ObjectId` type. The benefit of having the `Id` field represented as an `ObjectId`, is that we can offload the generating of unique ID's for the user object to MongoDB.

The `[BsonIgnoreIfNull]` attribute specifies that if the `Claims` or `Roles` collections on the user object are null, serializing/deserializing shouldn't be attempted. This helps to avoid any unwanted serialization issues within the MongoDB driver.

## ISSORole Implementation

Write an implementation of the `ISSORole` interface.

    public class CustomSSORole : ISSORole
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id { get; set; }
        public string Description { get; set; }
        public bool NonEditable { get; set; }
        public string Name { get; set; }
    }

## ISSOClaimType Implementation

Now, the implementation of the `ISSOClaimType` interface

    public class CustomSSOClaimType : ISSOClaimType
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id { get; set; }
        public string Name { get; set; }
        public string DisplayName { get; set; }
        public string Description { get; set; }
        public bool IsRequired { get; set; }
        public bool IsReserved { get; set; }
        public SSOClaimValueType ValueType { get; set; }
        public string RegularExpressionValidationRule { get; set; }
        public string RegularExpressionValidationFailureDescription { get; set; }
        public bool IsUserEditable { get; set; }
        public ICollection<string> AllowedValues { get; set; }
    }

## ISSOClaim Implementation

And finally, the implementation of the `ISSOClaim` interface

    public class CustomSSOClaim : ISSOClaim
    {
        [BsonId]
        public int Id { get; set; }
        public string ClaimType { get; set; }
        public string ClaimValue { get; set; }
    }

## MongoDB Driver Class Mapping

You must also register the class mapping within the MongoDB driver for the implementations. To do this, update your `Program.cs` class with the following:

    BsonClassMap.RegisterClassMap<CustomSSOUser>();
    BsonClassMap.RegisterClassMap<CustomSSORole>();
    BsonClassMap.RegisterClassMap<CustomSSOClaim>();
    BsonClassMap.RegisterClassMap<CustomSSOClaimType>();

It's not a requirement for this code to be in the `Program.cs` class, you could also place it in the stores. However, the registration of the class maps must be present before any calls to the database wherein deserialization would take place.

## Store Implementations

Now you must create implementations of the stores. This will mean creating implementations of the following interfaces:

- ISSOUserStore
- ISSORoleStore
- ISSOClaimTypeStore

This will be the stores that are called withing AdminUI's service layer and contain all the methods AdminUI needs to work.

## ISSOUserStore Implementation

Start by implementing the `ISSOUserStore`. This is the largest store in terms of implemented methods.

    // Implementation shortened for brevity

    public class UserStore : ISSOUserStore
    {
        private readonly IMongoCollection<CustomSSOUser> dbUsers;
        private readonly PasswordHasher<CustomSSOUser> passwordHasher;

        public UserStore(IOptions<IdentityStoreDatabaseSettings> dbSettings)
        {
            var mongoClient = new MongoClient(
                dbSettings.Value.ConnectionString);

            var mongoDatabase = mongoClient.GetDatabase(
                dbSettings.Value.DatabaseName);

            dbUsers = mongoDatabase.GetCollection<CustomSSOUser>(
                dbSettings.Value.UsersCollectionName);

            passwordHasher = new PasswordHasher<CustomSSOUser>();
        }

        public async Task<ISSOUser> CreateUser(ISSOUser user)
        {
           var newUser = new CustomSSOUser
            {
                UserName = user.UserName,
                Email = user.Email,
                ConcurrencyStamp = Guid.NewGuid().ToString(),
                TwoFactorEnabled = false,
                FirstName = user.FirstName,
                LastName = user.LastName,
                IsDeleted = false,
                LockoutEnabled = false,
            };

            foreach (var claim in user.Claims)
            {
                newUser.Claims.Add(claim);
            }

            await dbUsers.InsertOneAsync(newUser);

            var createdUserQuery = await dbUsers.FindAsync(u => u.UserName == newUser.UserName);
            return await createdUserQuery.FirstOrDefaultAsync();
        }

You can see how to initialise the `dbUsers` collection using the injected `IdentityStoreDatabaseSettings` configured in the `Program.cs` file. In production, you may want to use a factory to abstract out this functionality.

## ISSORoleStore Implementation

Next, implement the `ISSORoleStore`.

    //Implementation shortened for brevity

    public class RoleStore : ISSORoleStore
    {
        private readonly IMongoCollection<CustomSSOUser> dbUsers;
        private readonly IMongoCollection<CustomSSORole> dbRoles;

        public RoleStore(IOptions<IdentityStoreDatabaseSettings> dbSettings)
        {
            var mongoClient = new MongoClient(
                dbSettings.Value.ConnectionString);

            var mongoDatabase = mongoClient.GetDatabase(
                dbSettings.Value.DatabaseName);

            dbUsers = mongoDatabase.GetCollection<CustomSSOUser>(
                dbSettings.Value.UsersCollectionName);

            dbRoles = mongoDatabase.GetCollection<CustomSSORole>(
                dbSettings.Value.RolesCollectionName);
        }

        public async Task<ISSORole> CreateRole(ISSORole role)
        {
            await dbRoles.InsertOneAsync(new CustomSSORole
            {
                Name = role.Name,
                Description = role.Description,
                NonEditable = role.NonEditable
            });

            var rolesInsertedName = await dbRoles.FindAsync(r => r.Name == role.Name);

            return await rolesInsertedName.FirstOrDefaultAsync();
        }

## ISSOClaimTypeStore Implementation

Finally, implement the `ISSOClaimTypeStore`

    //Implementation shortened for brevity

    public class ClaimTypeStore : ISSOClaimTypeStore
    {
        private readonly IMongoCollection<CustomSSOClaimType> dbClaimTypes;

        public ClaimTypeStore(IOptions<IdentityStoreDatabaseSettings> dbSettings)
        {
            var mongoClient = new MongoClient(
                dbSettings.Value.ConnectionString);

            var mongoDatabase = mongoClient.GetDatabase(
                dbSettings.Value.DatabaseName);

            dbClaimTypes = mongoDatabase.GetCollection<CustomSSOClaimType>(
                dbSettings.Value.ClaimTypesCollectionName);
        }

        public async Task<ISSOClaimType> CreateClaimType(ISSOClaimType claimType)
        {
            var newClaimType = new CustomSSOClaimType
            {
                Name = claimType.Name,
                DisplayName = claimType.DisplayName,
                Description = claimType.Description,
                IsRequired = claimType.IsRequired,
                IsReserved = claimType.IsReserved,
                ValueType = claimType.ValueType,
                RegularExpressionValidationRule = claimType.RegularExpressionValidationRule,
                RegularExpressionValidationFailureDescription = claimType.RegularExpressionValidationFailureDescription,
                IsUserEditable = claimType.IsUserEditable,
                AllowedValues = claimType.AllowedValues
            };

            await dbClaimTypes.InsertOneAsync(newClaimType);

            var claimTypeQuery = await dbClaimTypes.FindAsync(ct => ct.Name == claimType.Name);
            return await claimTypeQuery.FirstOrDefaultAsync();
        }

## ISSOStoreFactory Implementation

Next, you will create an implementation of the `ISSOStoreFactory`. This is the factory used by the AdminUI service layer to create the instances of the stores you have already written.

    public class CustomSSOStoreFactory : ISSOStoreFactory
    {
        private readonly IOptions<IdentityStoreDatabaseSettings> dbSettings;

        public CustomSSOStoreFactory(IOptions<IdentityStoreDatabaseSettings> dbSettings)
        {
            this.dbSettings = dbSettings ?? throw new ArgumentNullException(nameof(dbSettings));
        }

        public ISSOUserStore CreateUserStore()
        {
            return new UserStore(dbSettings);
        }

        public ISSORoleStore CreateRoleStore()
        {
            return new RoleStore(dbSettings);
        }

        public ISSOClaimTypeStore CreateClaimTypeStore()
        {
            return new ClaimTypeStore(dbSettings);
        }
    }

## Summary

In this document you have created a NoSQL implementation of an Identity Store for AdminUI using the `Rsk.CustomIdentity` and `MongoDB.Driver` NuGet packages.

The full source code for this sample can be found on our GitHub page

Previous Next