Skip to content

Security

Michael Vivet edited this page Jan 8, 2025 · 119 revisions

Table of Contents


Summary

Securing the application is about user identity, and authentication and authorization.

Nano provides everything required to effectively manage user identities, and still leveraging full control for customizing polices, claims and roles. The default database created by Nano includes all the required tables, and contains all the technical identity data. This is extended with a custom generic user model during signup.

The DefaultIdentityManager exposes methods for handling all the interaction with user identities, such as login, signup, change email, etc. The TransientIdentityManager exposes methods for authentication against non-persisted user identities. This could be through external provider login, or with the built-in administrator user.

NOTE: Without a configured IDataProvider, the identity features are highly limited. Only Transient operations will be available.


Object Model

Security contains many models, handling everything from login to change password, or logging in with an external provider. Most are straight forward, and is used as parameter for just one method in the IdentityManager.

When adding the initial database migration snapshot, models and mappings related to identity is injected. The models are based on Microsoft.AspNetCore.Identity library.

Normally, you would derive your custom user from the IdentityUser<T>, when building a store for user identity. This approach is not possible when encapsulating functionality, as the consumer would have to deal with too many factors, such as generic parameters and constraints. By using a composite user model, where the identity and user is separated, Nano is able to manage the identity part without having to worry about custom properties. The Signup methods in Nano automatically links the two tables, and when a CustomUser is retrieved, the related IdentityUser data is retrieved as well.


Configuration

The Security section of the configuration defines behavior related to authentication and authorization in the application. The section is deserialized into an instance of SecurityOptions, and injected as dependency during startup, thus available for injection throughout the application.

See Appendix - App Settings for details about the app section and the meaning of the variables.

Security Section
"Security": {
    "TokensExpirationInHours": 24,
    "Jwt": {
        "IsEnabled": true,
        "Issuer": "issuer",
        "Audience": "audience",
        "PublicKey": null,
        "PrivateKey": null,
        "ExpirationInMinutes": 60,
        "RefreshExpirationInHours": 72
    },
    "Jwt": {
        "IsEnabled": false,
        "Secret": null,
    },
    "User": {
        "AdminUsername": "admin",
        "AdminPassword": "password",
        "AdminEmailAddress": "[email protected]",
        "AllowedUserNameCharacters": null,
        "DefaultRoles": [
            "reader",
            "writer",
            "service"
        ]
    },
    "SignIn": {
        "RequireConfirmedEmail": false,
        "RequireConfirmedPhoneNumber": false
    },
    "Lockout": {
        "AllowedForNewUsers": true,
        "MaxFailedAccessAttempts": 3,
        "DefaultLockoutTimeSpan": "00:30:00"
    },
    "Password": {
        "RequireDigit": false,
        "RequireNonAlphanumeric": false,
        "RequireLowercase": false,
        "RequireUppercase": false,
        "RequiredLength": 5,
        "RequiredUniqueCharacters": 0
    },
    "ExternalLogins": {
        "Google": {
            "ClientId": null,
            "ClientSecret": "N/A",
            "Scopes": [
            ]
        },
        "Facebook": {
            "AppId": null,
            "AppSecret": "N/A",
            "Scopes": [
            ]
        },
        "Microsoft": {
            "TenantId": null,
            "ClientId": null,
            "ClientSecret": "N/A",
            "Scopes": [
            ]
        }
    }
}

Model

The identity is associated with the user through a simple foreign key navigation, and is included when the user is queried, by deriving the custom user model from the DefaultEntityUser.

public class DefaultEntityUser : DefaultEntity
{
    [MaxLength(128)]
    public virtual string IdentityUserId { get; set; }

    [Include]
    public virtual IdentityUser IdentityUser { get; set; }
}

This isolates and hides all functionality related to the account of a user, and allows to work solely with the user relevant to the application.

NOTE: All identity email addresses, user names and phone numbers must be unique or null. At least one must not be null.

Mapping

When mapping the user model, derive the mapping implementation from DefaultEntityUserMapping<TEntity>. Besides that, mapping is no different than models not having an identity associated.

public class DefaultEntityUserMapping<TEntity> : DefaultEntityMapping<TEntity> 
    where TEntity : DefaultEntityUser
{
    public override void Map(EntityTypeBuilder<TEntity> builder)
    {
        base.Map(builder);
`
        builder
            .HasOne(x => x.IdentityUser)
            .WithOne()
            .IsRequired();
    }
}

Roles & Claims

Roles

Name Type Description
Guest built-in Currently, not authorized to do anything.
Reader built-in Authorized to read.
Writer built-in Authorized to read and write
Service built-in Authorized to all services.
Administrator built-in Full access to everything.
MyRole custom Custom role specified during signup or login.

Claims

Name Type Description
AppId built-in The id of the application. Set during logon, and used for supporting multiple refresh tokens. Default value: "Default"
Id built-in The user id.
Email built-in The user's email address.
Name built-in The username
MyClaim custom Custom claim specified during signup or login.

Authentication

Nano supports authenticating with user credentials (username and password), and also using one of the supported external providers.

A successful authentication returns a Nano AccessToken.

{
    "AppId": null,
    "UserId": null,
    "Token": null,
    "ExpireAt": null,
    "IsExpired": false,
    "RefreshToken": {
        "Token": null,
        "ExpireAt": null,
        "IsExpired": false,
    },
}

The jwt-token contains the following claims and values.

{
  "appId": "Default",
  "jti": "74ec40fe-bb18-4bd6-8ec5-51b37f9c8a5c",
  "sub": "08d9da95-9a3b-4ec6-83b0-fa11da6bae7e",
  "name": "[email protected]",
  "email": "[email protected]",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [
    "service",
    "administrator"
  ],
  "nbf": 1111111111,
  "exp": 1111111111,
  "iss": "development.nano",
  "aud": "development.nano"
}

See Controller Authentication for details about how to authenticate with your web application.

Api Key

It's also possible to authenticate using an api-key, by setting the header x-api-key with a valid api-key.

Api-keys can be managed through the ``IdentityManager```.

External Providers

Nano supports the following external providers.

Name Type Description
Google Implicit Google authentication using implicit flow. No token refresh possible.
Facebook Implicit Facebook authentication using implicit flow. No token refresh possible.
Microsoft Auth-Code Microsoft authentication using auth code flow.

When logging in with an external provider through a single-page-application, Nano finalizes the flow by validating the external response. On successful validation a Nano jwt-token is created, wherein the retrieved external access-token is embedded, should it later be needed to authorize against the external provider.

Scopes for id, email and username should be enabled for external providers.


Admin User

Nano comes with a built-in administrator, defined in the Security Section, as shown below. The administrator user will be created during start-up, if it doesn't already exist.

The administrator has unrestricted permissions, and may access any part of the application.

"Security": {
  "User": {
    "AdminUsername": "admin",
    "AdminPassword": "<<secret>>",
  }
}

The administrator will only be created if IsAuth=True in the Security Section.

"Security": {
    "IsAuth": false,
}

Identity Manager

The DefaultIdentityManager, encapsulates features of Microsoft Identity (UserManager and SignInManager), exposes atomic methods for managing user identity, and simplifies using custom user identities, by separating the identity from the user.

The TransientIdentityManager contains methods for logging in users without having a identity store. This can be used to login transiently using the administrator user defined in the configuration, or by using one of the supported external providers. Transient logins can't be refreshed.

Default Identity Manager methods

Method Description
GetUserAsync Get a identity user.
SignInAsync Signs in the user.
SignInExternalAsync Signs in a user with external provider login.
GetExternalProvidersAsync Gets all the configured external provider schemes.
SignInRefreshAsync Refreshes the login of a signed-in user.
SignOutAsync Logs out a user.
SignUpAsync Registers a new user.
SignUpExternalAsync Registers a new user using an external login provider.
RemoveExternalLoginAsync Removes the external login of a user.
SetUsernameAsync Sets a username for a user.
SetPasswordAsync Sets a password for a user.
ResetPasswordAsync Resets the password of a user.
GetPasswordOptions Get the password options.
ChangePasswordAsync Changes the password of a user.
ChangeEmailAsync Changes the email address of a user
ConfirmEmailAsync Confirms the email of a user.
ChangePhoneNumberAsync Changes the phone number of a user
ConfirmPhoneNumberAsync Confirms the phone number of a user.
VerifyCustomTokenAsync Verifies a custom token.
GenerateResetPasswordTokenAsync Generates an reset password token for a user.
GenerateConfirmEmailTokenAsync Generates an email confirmation token for a user.
GenerateChangeEmailTokenAsync Generates an change email token for a user.
GenerateConfirmPhoneNumberTokenAsync Generates an phone number confirmation token for a user.
GenerateChangePhoneNumberTokenAsync Generates an change phone number token for a user.
GenerateCustomTokenAsync Generates a custom token for a user.
GetApiKeysAsync Get the api-keys if a user.
CreateApiKeyAsync Create a new api-key
ValidateApiKeyAsync Validate an api-key.
EditApiKeyAsync Edit an api-key.
RevokeApiKeyAsync Revoke an api-key.
GetRolesAsync Get all roles.
CreateRoleAsync Create a new role.
DeleteRoleAsync Delete a role.
GetUserRolesAsync Get the roles associated with a user.
AssignUserRoleAsync Assign a roles to a user.
RemoveUserRoleAsync Remove a role from a user.
GetUserClaimAsync Get the claims of a user.
GetUserClaimsAsync Get a claim of a user.
AssignUserClaimAsync Assign a claim to a user.
RemoveUserClaimAsync Remove a claim from a user.
GetRoleClaimAsync Get a role claim.
GetRoleClaimsAsync Get role claims.
AssignRoleClaimAsync Assign a role claim.
RemoveRoleClaimAsync Remove a role claim.
CreateUser Creates a custom user, and the identity user association.
DeleteIdentityUser Delete the Identity user.

Transient Identity Manager methods

Method Description
SignInAdminTransientAsync Signs in the admin user, defined in the config security section.
SignInExternalTransientAsync Signs in a use with external login info transiently (no identity backing-store).
GetExternalProviderLoginData Get the external login data from an external provider. Basically, signing in the user, and getting the external token, and other data.

Injected Services

When building the eventing provider, dependencies related to eventing is configured and initialized.

Dependencies:
  • Nano.Security.SecurityOptions
  • Nano.Security.BaseIdentityManager
  • Nano.Security.DefaultIdentityManager
  • Nano.Security.TransientIdentityManager

For a full list of services and dependencies, see Injected Dependencies


Clone this wiki locally