Can we use ASP.NET Identity in Domain Driven Design?
Asked Answered
P

4

18

Our team decided to use Domain Driven Design architecture for our project. Now the discussion is going on for, "can we use ASP.NET Identity in DDD?".

Is there any disadvantages on using ASP.NET identity in DDD design.

I'm in a confusion to make a decision on it.

I have searched for it, but I didn't get any idea.

Any help would be appreciable.

Plait answered 1/4, 2014 at 6:23 Comment(2)
What do you mean by "in"? Identity management is typically a separate bounded context.Alphonsealphonsine
@BartłomiejSzypelow can we implement asp.net identity in DDD and is there any link or article relevant to this? If we implement asp.net identity in DDD then does IdentityUser comes under Domain model layer and IdentityDbContext goes to the DAL?Plait
L
33

The questions reveals several misconceptions:

It appears that you perceive the domain model as some monolithic model where you put every piece of application in. Instead, concentrate on strategic patterns to distinguish Bounded Contexts. Consider the domain as a composition of several loosely interconnected components. Then identify what your core domain is and apply DDD tactical patterns there. Not every ccomponent needs DDD. Some of them even should not use DDD. Especially - generic domains, like Authentication.

DDD is technology agnostic (to some point) so yes, you can use ASP.NET Identity or whatever library you like.

Authentication typically belongs to the Application layer, not Domain layer.

However - if in your domain there is a concept of a user/client/person, it might be required to use the identity provided by the identity component. But you have to understand that the meaning of User in your bounded context is different than meaning of User in Identity component. These are not the same concept. Although they both refer to the same physical person sitting somewhere and clicking on your app's GUI, they are 2 different models (or projections) of him that serve different purposes. So you shouldn't just reuse the ASP.NET User class in your bounded context.

Instead - separate contexts should communicate via an anticorruption layer. Basically you have to make some service (interface only) in your bounded context that produces context-specific User objects. The implementation of that interface made in infrastructure layer will be a wrapper of ASP.NET Identity that gets ASP.NET Identity user and produce corresponding bounded context's user.

Logic answered 7/4, 2014 at 11:12 Comment(3)
+1 this is a great answer, although I fear it may be lost on the OP.Vauban
Why does Authentication belong to Application Layer. Having Anaemic User model is also Anti DDD. Authentication can have its own domain object with behaviour.Adaiha
Authentication/Authorization is application level concern as it's primary purpose is to decide whether or not user's action is permissible. It involves hashing logic and sometimes other infrastructure level concerns such as two-factor authentication.Faludi
D
2

I am new to DDD. But I achieved integration with Identity and DDD. Although I doubt it truly sticks to the priciples of DDD.

I started with the Entity:

public partial class User : IdentityUser {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager) {
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }
        /// <summary>
        /// The Date the User Registered for general information purposes
        /// </summary>
        public DateTime DateRegistered { get; set; }
    }

Then the Interface:

public interface IUserRepository:IBaseRepository<User> {
        /// <summary>
        /// Register User to Identity Database
        /// </summary>
        /// <param name="userManager">User Manager to Handle Registration</param>
        /// <param name="user">User to add to database</param>
        /// <param name="password">User's password</param>
        /// <returns>Identity Result</returns>
        Task<IdentityResult> Register(UserManager<User, string> userManager, User user, string password);
        /// <summary>
        /// Login User
        /// </summary>
        /// <param name="signinManager">Signin Manager to handle login</param>
        /// <param name="email">Email of user</param>
        /// <param name="password">Password of user</param>
        /// <param name="rememberMe">Boolean if the user wants to be remembered</param>
        /// <returns>SignIn Status</returns>
        Task<SignInStatus> Login(SignInManager<User, string> signinManager, string email, string password, bool rememberMe);
        /// <summary>
        /// Verify that code sent to User is valid
        /// </summary>
        /// <param name="signinManager">Signin Manager to handle verification</param>
        /// <param name="provider">Provider of the code</param>
        /// <param name="code">The code</param>
        /// <param name="rememberMe">Boolean if user wants to be remembered</param>
        /// <param name="rememberBrowser">Boolean if browser should be remembered</param>
        /// <returns>SignIn Status</returns>
        Task<SignInStatus> VerifyCode(SignInManager<User, string> signinManager, string provider, string code, bool rememberMe, bool rememberBrowser);
        /// <summary>
        /// Confirm email of User
        /// </summary>
        /// <param name="userManager">User Manager to handle confirmation</param>
        /// <param name="userId">String user Id of the User</param>
        /// <param name="code">User code sent in Email</param>
        /// <returns>Identity Result</returns>
        Task<IdentityResult> ConfirmEmail(UserManager<User, string> userManager, string userId, string code);
        void ForgotPassword();
        void ForgotPasswordConfirmation();
        void ResetPassword();
        void ResetPasswordConfirmation();
        void ExternalLogin();
        void SendCode();
        void ExternalLoginCallback();
        void ExternalLoginConfirmation();
        /// <summary>
        /// Log off user from the Application
        /// </summary>
        /// <param name="AuthenticationManager">Application Manager to handle Sign out</param>
        void Logoff(IAuthenticationManager AuthenticationManager);
        /// <summary>
        /// Get user based on their Email
        /// </summary>
        /// <param name="Email">Email of user</param>
        /// <returns>User</returns>
        User GetUser(string Email);
        /// <summary>
        /// Get User by their GUID
        /// </summary>
        /// <param name="ID">GUID</param>
        /// <returns>User</returns>
        User GetUserById(string ID);
    }

Then the Repository:

public class UserRepository : BaseRepository<User>, IUserRepository {
        /// <summary>
        /// Confirm email of User
        /// </summary>
        /// <param name="userManager">User Manager to handle confirmation</param>
        /// <param name="userId">String user Id of the User</param>
        /// <param name="code">User code sent in Email</param>
        /// <returns>Identity Result</returns>
        public async Task<IdentityResult> ConfirmEmail(UserManager<User, string> userManager, string userId, string code) =>
            await userManager.ConfirmEmailAsync(userId, code);

        public void ExternalLogin() {
            throw new NotImplementedException();
        }

        public void ExternalLoginCallback() {
            throw new NotImplementedException();
        }

        public void ExternalLoginConfirmation() {
            throw new NotImplementedException();
        }

        public void ForgotPassword() {
            throw new NotImplementedException();
        }

        public void ForgotPasswordConfirmation() {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Get user based on their Email
        /// </summary>
        /// <param name="Email">Email of user</param>
        /// <returns>User</returns>
        public User GetUser(string Email) =>
            _context.Users.Where(p => p.Email == Email).FirstOrDefault();
        /// <summary>
        /// Get User by their GUID
        /// </summary>
        /// <param name="ID">GUID</param>
        /// <returns>User</returns>
        public User GetUserById(string ID) => 
            _context.Users.Find(ID);
        /// <summary>
        /// Login User
        /// </summary>
        /// <param name="signinManager">Signin Manager to handle login</param>
        /// <param name="email">Email of user</param>
        /// <param name="password">Password of user</param>
        /// <param name="rememberMe">Boolean if the user wants to be remembered</param>
        /// <returns>SignIn Status</returns>
        public async Task<SignInStatus> Login(SignInManager<User, string> signinManager, string email, string password, bool rememberMe) =>
            await signinManager.PasswordSignInAsync(email, password, rememberMe, shouldLockout: false);
        /// <summary>
        /// Log off user from the Application
        /// </summary>
        /// <param name="AuthenticationManager">Application Manager to handle Sign out</param>
        public void Logoff(IAuthenticationManager AuthenticationManager) {
            AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
        }
        /// <summary>
        /// Register User to Identity Database
        /// </summary>
        /// <param name="userManager">User Manager to Handle Registration</param>
        /// <param name="user">User to add to database</param>
        /// <param name="password">User's password</param>
        /// <returns>Identity Result</returns>
        public async Task<IdentityResult> Register(UserManager<User, string> userManager, User user, string password) =>
            await userManager.CreateAsync(user, password);

        public void ResetPassword() {
            throw new NotImplementedException();
        }

        public void ResetPasswordConfirmation() {
            throw new NotImplementedException();
        }

        public void SendCode() {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Verify that code sent to User is valid
        /// </summary>
        /// <param name="signinManager">Signin Manager to handle verification</param>
        /// <param name="provider">Provider of the code</param>
        /// <param name="code">The code</param>
        /// <param name="rememberMe">Boolean if user wants to be remembered</param>
        /// <param name="rememberBrowser">Boolean if browser should be remembered</param>
        /// <returns>SignIn Status</returns>
        public async Task<SignInStatus> VerifyCode(SignInManager<User, string> signinManager, string provider, string code, bool rememberMe, bool rememberBrowser) =>
            await signinManager.TwoFactorSignInAsync(provider, code, isPersistent: rememberMe, rememberBrowser: rememberBrowser);
    }

IService:

public interface IUserService {
        /// <summary>
        /// Register User to Identity Database
        /// </summary>
        /// <param name="userManager">User Manager to Handle Registration</param>
        /// <param name="user">User to add to database</param>
        /// <param name="password">User's password</param>
        /// <returns></returns>
        Task<IdentityResult> Register(UserManager<User, string> userManager, User user, string password);
        /// <summary>
        /// Login User
        /// </summary>
        /// <param name="signinManager">Signin Manager to handle login</param>
        /// <param name="email">Email of user</param>
        /// <param name="password">Password of user</param>
        /// <param name="rememberMe">Boolean if the user wants to be remembered</param>
        /// <returns></returns>
        Task<SignInStatus> Login(SignInManager<User, string> signinManager, string email, string password, bool rememberMe);
        /// <summary>
        /// Verify that code sent to User is valid
        /// </summary>
        /// <param name="signinManager">Signin Manager to handle verification</param>
        /// <param name="provider">Provider of the code</param>
        /// <param name="code">The code</param>
        /// <param name="rememberMe">Boolean if user wants to be remembered</param>
        /// <param name="rememberBrowser">Boolean if browser should be remembered</param>
        /// <returns></returns>
        Task<SignInStatus> VerifyCode(SignInManager<User, string> signinManager, string provider, string code, bool rememberMe, bool rememberBrowser);
        /// <summary>
        /// Confirm email of User
        /// </summary>
        /// <param name="userManager">User Manager to handle confirmation</param>
        /// <param name="userId">String user Id of the User</param>
        /// <param name="code">User code sent in Email</param>
        /// <returns></returns>
        Task<IdentityResult> ConfirmEmail(UserManager<User, string> userManager, string userId, string code);
        void ForgotPassword();
        void ForgotPasswordConfirmation();
        void ResetPassword();
        void ResetPasswordConfirmation();
        void ExternalLogin();
        void SendCode();
        void ExternalLoginCallback();
        void ExternalLoginConfirmation();
        /// <summary>
        /// Log off user from the Application
        /// </summary>
        /// <param name="AuthenticationManager">Application Manager to handle Sign out</param>
        void Logoff(IAuthenticationManager AuthenticationManager);
        /// <summary>
        /// Get user based on their Email
        /// </summary>
        /// <param name="Email">Email of user</param>
        /// <returns>User</returns>
        User GetUser(string Email);
        /// <summary>
        /// Get User by their GUID
        /// </summary>
        /// <param name="ID">GUID</param>
        /// <returns>User</returns>
        User GetUserById(string ID);
    }

Service:

public class UserService : ServiceBase, IUserService {

        #region Private Field

        private IUserRepository _userRepository;

        #endregion

        #region Constructor
        /// <summary>
        /// Constructor to initialise User Repository
        /// </summary>
        /// <param name="userRepository"></param>
        public UserService(IUserRepository userRepository) {
            _userRepository = userRepository;
        }

        #endregion

        #region Methods
        /// <summary>
        /// Confirm email of User
        /// </summary>
        /// <param name="userManager">User Manager to handle confirmation</param>
        /// <param name="userId">String user Id of the User</param>
        /// <param name="code">User code sent in Email</param>
        /// <returns>Identity Result</returns>
        public Task<IdentityResult> ConfirmEmail(UserManager<User, string> userManager, string userId, string code) =>
            _userRepository.ConfirmEmail(userManager, userId, code);

        public void ExternalLogin() {
            throw new NotImplementedException();
        }

        public void ExternalLoginCallback() {
            throw new NotImplementedException();
        }

        public void ExternalLoginConfirmation() {
            throw new NotImplementedException();
        }

        public void ForgotPassword() {
            throw new NotImplementedException();
        }

        public void ForgotPasswordConfirmation() {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Get user based on their Email
        /// </summary>
        /// <param name="Email">Email of user</param>
        /// <returns>User</returns>
        public User GetUser(string Email) {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Get User by their GUID
        /// </summary>
        /// <param name="ID">GUID</param>
        /// <returns>User</returns>
        public User GetUserById(string ID) {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Login User
        /// </summary>
        /// <param name="signinManager">Signin Manager to handle login</param>
        /// <param name="email">Email of user</param>
        /// <param name="password">Password of user</param>
        /// <param name="rememberMe">Boolean if the user wants to be remembered</param>
        /// <returns>SignIn Status</returns>
        public Task<SignInStatus> Login(SignInManager<User, string> signinManager, string email, string password, bool rememberMe) =>
            _userRepository.Login(signinManager, email, password, rememberMe);
        /// <summary>
        /// Log off user from the Application
        /// </summary>
        /// <param name="AuthenticationManager">Application Manager to handle Sign out</param>
        public void Logoff(IAuthenticationManager AuthenticationManager) {
            _userRepository.Logoff(AuthenticationManager);
        }
        /// <summary>
        /// Register User to Identity Database
        /// </summary>
        /// <param name="userManager">User Manager to Handle Registration</param>
        /// <param name="user">User to add to database</param>
        /// <param name="password">User's password</param>
        public Task<IdentityResult> Register(UserManager<User, string> userManager, User user, string password) =>
            _userRepository.Register(userManager, user, password);

        public void ResetPassword() {
            throw new NotImplementedException();
        }

        public void ResetPasswordConfirmation() {
            throw new NotImplementedException();
        }

        public void SendCode() {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Verify that code sent to User is valid
        /// </summary>
        /// <param name="signinManager">Signin Manager to handle verification</param>
        /// <param name="provider">Provider of the code</param>
        /// <param name="code">The code</param>
        /// <param name="rememberMe">Boolean if user wants to be remembered</param>
        /// <param name="rememberBrowser">Boolean if browser should be remembered</param>
        /// <returns>SignIn Status</returns>
        public Task<SignInStatus> VerifyCode(SignInManager<User, string> signinManager, string provider, string code, bool rememberMe, bool rememberBrowser) =>
            _userRepository.VerifyCode(signinManager, provider, code, rememberMe, rememberBrowser);

        #endregion

    }
Destructible answered 21/1, 2017 at 19:53 Comment(1)
I think you have created a dependency in your domain layer you should avoid: User:IdentityUser .Beano
B
1

You can use anything you like. But be aware of pollution particular solution is going to make. If your domain model gets messed up with hundreds of lines of asp.net technical kind of plumbing code that makes your domain logic hard to perceive and you are missing point of DDD.

In ideal situation - your domain model should depend only on programming language.

Also - you might find something useful from my long time ago implementation of user session related code.

Berger answered 3/4, 2014 at 8:42 Comment(0)
A
0

You should define common interfaces or abstractions that the Domain layer can use without knowing the concrete implementation details from the Infrastructure layer. In your solution, create a separate project or folder called "ApplicationCore" or "SharedKernel" to define common interfaces or abstractions that the Domain layer can depend on or just simply put these interfaces in the Domain layer.

YourSolution/
├── YourApp/
│   ├── ...
│
├── YourApp.Infrastructure/
│   ├── Data/
│   │   ├── ApplicationDbContext.cs
│   │
│   ├── Identity/
│   │   ├── ApplicationUser.cs
│   │
│   ├── ...
│
├── YourApp.ApplicationCore/      // Create this project or folder
│   ├── Interfaces/
│   │   ├── IApplicationUser.cs    // Define an interface for ApplicationUser
│   │
│   ├── ...
│
├── ...

In the Domain layer:

public interface IApplicationUser
{
    string Id { get; }
    string UserName { get; set; }
    // Define other properties and methods needed by the Domain layer.
}

In the Infrastructure layer:

public class ApplicationUser : IdentityUser, IApplicationUser
{
   // Implement the interface members here.
}
Appolonia answered 7/9, 2023 at 21:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.