One place for hosting & domains

      Authentication

      Easy Node Authentication: Linking All Accounts Together

      Note: This article is part of our Easy Node Authentication series.

      • Getting Started and Local Authentication
      • Facebook
      • Twitter
      • Google
      • Linking All Accounts Together
      • Upgrading Our “Easy Node Authentication” Series to ExpressJS 4.0

      Introduction

      This will be the final article in our Easy Node Authentication Series. We will be using all of the previous articles together.

      Note: Edit 11/18/2017: Updated to reflect Facebook API changes.

      This article will combine all the different Node Passport Strategies so that a user will be able to have one account and link all their social networks together.

      There are many changes that need to take place from the previous articles to accomplish this. Here are the main cases we have to account for when moving from authenticating with only 1 account versus multiple accounts.

      Scenarios to Account For

      • Linking Accounts: Check if a user already exists in the database. If they do, then add a social account to their user profile
      • Account Creation: If a user does not exist in the database, then create a user profile
      • Unlinking: Unlinking social accounts
      • Reconnecting: If a user unlinked a social account, but wants to reconnect it

      We’ll be going through each of these scenarios and updating our previous code to account for them.

      We’ll be working with the Local Strategy and the Facebook Strategy to demonstrate linking accounts. The tactics used for the Facebook Strategy will carry over to Twitter and Google.

      In order to add linking accounts to our application, we will need to:

      • Update our Strategies
      • Add new routes
      • Update our views for linking and unlinking

      When looking at the way we set up our user model, we deliberately set up all the user accounts to be set up within their own object. This ensures that we can link and unlink different accounts as our user sees fit. Notice that the social accounts will use token and id while our local account will use email and password.

      app/models/user.js

          ...
          var userSchema = mongoose.Schema({
      
              local            : {
                  email        : String,
                  password     : String,
              },
              facebook         : {
                  id           : String,
                  token        : String,
                  email        : String,
                  name         : String
              },
              twitter          : {
                  id           : String,
                  token        : String,
                  displayName  : String,
                  username     : String
              },
              google           : {
                  id           : String,
                  token        : String,
                  email        : String,
                  name         : String
              }
      
          });
          ...
      

      We have also added in email, name, displayName, and username for some accounts just to show that we can pull that information from the respective social connection.

      Once a user has linked all their accounts together, they will have one user account in our database, with all of these fields full.

      When we originally made these Strategies, we would use passport.authenticate. This is what we should be using upon first authentication of our user. But what do we do if they are already logged in? They will be logged in and their user stored in session when we want to link them to their current account.

      Luckily, Passport provides a way to “connect” a user’s account. They provide passport.authorize for users that are already authenticated. To read more on the usage, visit the Passport authorize docs.

      We will update our routes to handle the authorization first, and then we’ll update our Passport Strategies to handle the authorization.

      Let’s create our routes first so that we can see how we link everything together. In the past articles, we created our routes for authentication. Let’s create a second set of routes for authorization. Once we’ve done that, we’ll change our Strategy to accommodate the new scenarios.

      Our old routes will be commented to make a cleaner file.

      app/routes.js

          module.exports = function(app, passport) {
      
          
              
              
              
      
          
          
          
      
              
                  
                  
                  
      
                  
                  
                  
      
              
                  
                  app.get('/auth/facebook', passport.authenticate('facebook', { scope : 'email' }));
      
                  
                  app.get('/auth/facebook/callback',
                      passport.authenticate('facebook', {
                          successRedirect : '/profile',
                          failureRedirect : '/'
                      }));
      
              
                  
                  
      
              
                  
                  
      
          
          
          
      
              
                  app.get('/connect/local', function(req, res) {
                      res.render('connect-local.ejs', { message: req.flash('loginMessage') });
                  });
                  app.post('/connect/local', passport.authenticate('local-signup', {
                      successRedirect : '/profile', 
                      failureRedirect : '/connect/local', 
                      failureFlash : true 
                  }));
      
              
      
                  
                  app.get('/connect/facebook', passport.authorize('facebook', {
                    scope : ['public_profile', 'email']
                  }));
      
                  
                  app.get('/connect/facebook/callback',
                      passport.authorize('facebook', {
                          successRedirect : '/profile',
                          failureRedirect : '/'
                      }));
      
              
      
                  
                  app.get('/connect/twitter', passport.authorize('twitter', { scope : 'email' }));
      
                  
                  app.get('/connect/twitter/callback',
                      passport.authorize('twitter', {
                          successRedirect : '/profile',
                          failureRedirect : '/'
                      }));
      
      
              
      
                  
                  app.get('/connect/google', passport.authorize('google', { scope : ['profile', 'email'] }));
      
                  
                  app.get('/connect/google/callback',
                      passport.authorize('google', {
                          successRedirect : '/profile',
                          failureRedirect : '/'
                      }));
          };
      
          
          function isLoggedIn(req, res, next) {
              if (req.isAuthenticated())
                  return next();
      
              res.redirect('/');
          }
      

      As you can see, we have all the authentication routes and the routes to show our index and profile pages. Now we have added authorization routes which will look incredibly similar to our authentication routes.

      With our newly created routes, let’s update the Strategy so that our authorization routes are utilized.

      We will just update the Facebook and Local Strategies to get a feel for how we can accommodate all our different scenarios.

      When using the passport.authorize route, our user that is stored in session (since they are already logged in) will be passed to the Strategy. We will make sure we change our code to account for that.

      We’re going to show the old Strategy and then the new Strategy. Read the comments to get a full understanding of the changes.

      Old Facebook Strategy

      config/passport.js

          ...
              
              
              
              passport.use(new FacebookStrategy({
      
                  
                  clientID        : configAuth.facebookAuth.clientID,
                  clientSecret    : configAuth.facebookAuth.clientSecret,
                  callbackURL     : configAuth.facebookAuth.callbackURL
      
              },
      
              
              function(token, refreshToken, profile, done) {
      
                  
                  process.nextTick(function() {
      
                      
                      User.findOne({ 'facebook.id' : profile.id }, function(err, user) {
      
                          
                          
                          if (err)
                              return done(err);
      
                          
                          if (user) {
                              return done(null, user); 
                          } else {
                              
                              var newUser            = new User();
      
                              
                              newUser.facebook.id    = profile.id; 
                              newUser.facebook.token = token; 
                              newUser.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName; 
                              newUser.facebook.email = profile.emails[0].value; 
      
                              
                              newUser.save(function(err) {
                                  if (err)
                                      throw err;
      
                                  
                                  return done(null, newUser);
                              });
                          }
      
                      });
                  });
      
              }));
          ...
      

      Now we want the ability to authorize a user.

      New Facebook Strategy

          ...
              
              
              
              passport.use(new FacebookStrategy({
      
                  
                  clientID        : configAuth.facebookAuth.clientID,
                  clientSecret    : configAuth.facebookAuth.clientSecret,
                  callbackURL     : configAuth.facebookAuth.callbackURL,
                  passReqToCallback : true 
      
              },
      
              
              function(req, token, refreshToken, profile, done) {
      
                  
                  process.nextTick(function() {
      
                      
                      if (!req.user) {
      
                          
                          User.findOne({ 'facebook.id' : profile.id }, function(err, user) {
      
                              
                              
                              if (err)
                                  return done(err);
      
                              
                              if (user) {
                                  return done(null, user); 
                              } else {
                                  
                                  var newUser            = new User();
      
                                  
                                  newUser.facebook.id    = profile.id; 
                                  newUser.facebook.token = token; 
                                  newUser.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName; 
                                  newUser.facebook.email = profile.emails[0].value; 
      
                                  
                                  newUser.save(function(err) {
                                      if (err)
                                          throw err;
      
                                      
                                      return done(null, newUser);
                                  });
                              }
      
                          });
      
                      } else {
                          
                          var user            = req.user; 
      
                          
                          user.facebook.id    = profile.id;
                          user.facebook.token = token;
                          user.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName;
                          user.facebook.email = profile.emails[0].value;
      
                          
                          user.save(function(err) {
                              if (err)
                                  throw err;
                              return done(null, user);
                          });
                      }
      
                  });
      
              }));
          ...
      

      Now we have accounted for linking an account if a user is already logged in. We still have the same functionality from before, now we just check if the user is logged in before we take action.

      Using this new code in our Strategy, we will create a new user if they are not already logged in, or we will add our Facebook credentials to our user if they are currently logged in and stored in session.

      Other Strategies: The code for the Facebook Strategy will be the same for Twitter and Google. Just apply that code to both of those to get this working. We will also provide the full code so you can look at and reference it.

      Now that we have the routes that will pass our user to our new Facebook Strategy, let’s make sure our UI lets our user use the newly created routes.

      We will update our index.ejs and our profile.ejs to show all the login buttons on the home page, and all the accounts and link buttons on the profile page. Here is the full code for both with the important parts highlighted.

      Home Page index.ejs

      views/index.ejs

          <!doctype html>
          <html>
          <head>
              <title>Node Authentication</title>
              <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css">
              <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css">
              <style>
                  body        { padding-top:80px; }
              </style>
          </head>
          <body>
          <div class="container">
      
              <div class="jumbotron text-center">
                  <h1><span class="fa fa-lock"></span> Node Authentication</h1>
      
                  <p>Login or Register with:</p>
      
                  <a href="/login" class="btn btn-default"><span class="fa fa-user"></span> Local Login</a>
                  <a href="/signup" class="btn btn-default"><span class="fa fa-user"></span> Local Signup</a>
                  <a href="/auth/facebook" class="btn btn-primary"><span class="fa fa-facebook"></span> Facebook</a>
                  <a href="/auth/twitter" class="btn btn-info"><span class="fa fa-twitter"></span> Twitter</a>
                  <a href="/auth/google" class="btn btn-danger"><span class="fa fa-google-plus"></span> Google+</a>
              </div>
      
          </div>
          </body>
          </html>
      

      Profile Page profile.ejs

      views/profile.ejs

          <!doctype html>
          <html>
          <head>
              <title>Node Authentication</title>
              <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css">
              <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css">
              <style>
                  body        { padding-top:80px; word-wrap:break-word; }
              </style>
          </head>
          <body>
          <div class="container">
      
              <div class="page-header text-center">
                  <h1><span class="fa fa-anchor"></span> Profile Page</h1>
                  <a href="/logout" class="btn btn-default btn-sm">Logout</a>
              </div>
      
              <div class="row">
      
                  
                  <div class="col-sm-6">
                      <div class="well">
                          <h3><span class="fa fa-user"></span> Local</h3>
      
                          <% if (user.local.email) { %>
                              <p>
                                  <strong>id</strong>: <%= user._id %><br>
                                  <strong>email</strong>: <%= user.local.email %><br>
                                  <strong>password</strong>: <%= user.local.password %>
                              </p>
      
                              <a href="/unlink/local" class="btn btn-default">Unlink</a>
                          <% } else { %>
                              <a href="/connect/local" class="btn btn-default">Connect Local</a>
                          <% } %>
      
                      </div>
                  </div>
      
                  
                  <div class="col-sm-6">
                      <div class="well">
                          <h3 class="text-primary"><span class="fa fa-facebook"></span> Facebook</h3>
      
                          
                          <% if (user.facebook.token) { %>
                              <p>
                                  <strong>id</strong>: <%= user.facebook.id %><br>
                                  <strong>token</strong>: <%= user.facebook.token %><br>
                                  <strong>email</strong>: <%= user.facebook.email %><br>
                                  <strong>name</strong>: <%= user.facebook.name %><br>
                              </p>
      
                              <a href="/unlink/facebook" class="btn btn-primary">Unlink</a>
                          <% } else { %>
                              <a href="/connect/facebook" class="btn btn-primary">Connect Facebook</a>
                          <% } %>
      
                      </div>
                  </div>
              </div>
      
              <div class="row">
      
                  
                  <div class="col-sm-6">
                      <div class="well">
                          <h3 class="text-info"><span class="fa fa-twitter"></span> Twitter</h3>
      
                          
                          <% if (user.twitter.token) { %>
                              <p>
                                  <strong>id</strong>: <%= user.twitter.id %><br>
                                  <strong>token</strong>: <%= user.twitter.token %><br>
                                  <strong>display name</strong>: <%= user.twitter.displayName %><br>
                                  <strong>username</strong>: <%= user.twitter.username %>
                              </p>
      
                              <a href="/unlink/twitter" class="btn btn-info">Unlink</a>
                          <% } else { %>
                              <a href="/connect/twitter" class="btn btn-info">Connect Twitter</a>
                          <% } %>
      
                      </div>
                  </div>
      
                  
                  <div class="col-sm-6">
                      <div class="well">
                          <h3 class="text-danger"><span class="fa fa-google-plus"></span> Google+</h3>
      
                          
                          <% if (user.google.token) { %>
                              <p>
                                  <strong>id</strong>: <%= user.google.id %><br>
                                  <strong>token</strong>: <%= user.google.token %><br>
                                  <strong>email</strong>: <%= user.google.email %><br>
                                  <strong>name</strong>: <%= user.google.name %>
                              </p>
      
                              <a href="/unlink/google" class="btn btn-danger">Unlink</a>
                          <% } else { %>
                              <a href="/connect/google" class="btn btn-danger">Connect Google</a>
                          <% } %>
      
                      </div>
                  </div>
              </div>
      
          </div>
          </body>
          </html>
      

      Now we will have the links to each of our login methods. Then after they have logged in with one, the profile will check which accounts are already linked and which are not.

      If an account is not yet linked, it will show the Connect Button. If an account is already linked, then our view will show the account information and the unlink button.

      Remember that our user is passed to our profile view from the routes.js file.

      Connecting Local

      Our social accounts can easily be configured this way. The only problem currently is if a user wanted to connect to a local account. The problem comes in because they will need to see a signup page to add their email and password.

      We have already created a route to handle showing our new connection form (in our routes.js file: (app.get('connect/local'))). All we need to do is create the view that the route brings up.

      Create a file in your views folder: views/connect-local.ejs.

      views/connect-local.ejs

          <!doctype html>
          <html>
          <head>
              <title>Node Authentication</title>
              <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css">
              <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css">
              <style>
                  body        { padding-top:80px; }
              </style>
          </head>
          <body>
          <div class="container">
          <div class="col-sm-6 col-sm-offset-3">
      
              <h1><span class="fa fa-user"></span> Add Local Account</h1>
      
              <% if (message.length > 0) { %>
                  <div class="alert alert-danger"><%= message %></div>
              <% } %>
      
              
              <form action="/connect/local" method="post">
                  <div class="form-group">
                      <label>Email</label>
                      <input type="text" class="form-control" name="email">
                  </div>
                  <div class="form-group">
                      <label>Password</label>
                      <input type="password" class="form-control" name="password">
                  </div>
      
                  <button type="submit" class="btn btn-warning btn-lg">Add Local</button>
              </form>
      
              <hr>
      
              <p><a href="/profile">Go back to profile</a></p>
      
          </div>
          </div>
          </body>
          </html>
      

      This will look incredibly similar to our signup.ejs form. That’s because it really is. We pretty much just changed out the verbiage and the action URL for the form.

      Now when someone tries to connect a local account, they will be directed to this form, and then when submitted, they will be directed to our Local Strategy. That links the accounts!

      With just those routes and the update to our Passport Strategies, our application can now link accounts together! Take a look at a user in our database that has all their accounts linked using Robomongo:

      Linking accounts was easy. What about unlinking? Let’s say a user no longer wants their Facebook account linked.

      For our purposes, when a user wants to unlink an account, we will remove their token only. We will keep their id in the database just in case they realize their mistake of leaving and want to come back to join our application.

      We can do this all in our routes file. You are welcome to create a controller and do all this logic there. Then you would just call the controller from the routes. For simplicity’s sake, we’ll throw that code directly into our routes.

      Let’s add our unlinking routes after our newly created authorization routes.

      app/routes.js

          ...
      
          
          
          
      
          
          
          
          
          
          
      
              
              app.get('/unlink/local', function(req, res) {
                  var user            = req.user;
                  user.local.email    = undefined;
                  user.local.password = undefined;
                  user.save(function(err) {
                      res.redirect('/profile');
                  });
              });
      
              
              app.get('/unlink/facebook', function(req, res) {
                  var user            = req.user;
                  user.facebook.token = undefined;
                  user.save(function(err) {
                      res.redirect('/profile');
                  });
              });
      
              
              app.get('/unlink/twitter', function(req, res) {
                  var user           = req.user;
                  user.twitter.token = undefined;
                  user.save(function(err) {
                     res.redirect('/profile');
                  });
              });
      
              
              app.get('/unlink/google', function(req, res) {
                  var user          = req.user;
                  user.google.token = undefined;
                  user.save(function(err) {
                     res.redirect('/profile');
                  });
              });
          ...
      

      In these routes, we just pull a user’s information out of the request (session) and then remove the correct information. Since we already had created our links to these routes in profile.ejs, they will now work since we have created the routes finally.

      Now you can link an account and unlink an account.

      When trying to unlink, we will have to do a little more configuration for that to work. Since the id is already stored in the database, we will have to plan for that scenario when a user links an account that was already previously linked.

      After a user is unlinked, their id still lives in the database. Therefore, when a user logs in or relinks an account, we have to check if their id exists in the database.

      We will handle this in our Strategy. Let’s add to our Facebook Strategy.

      config/passport.js

          ...
              
              
              
              passport.use(new FacebookStrategy({
      
                  
                  clientID        : configAuth.facebookAuth.clientID,
                  clientSecret    : configAuth.facebookAuth.clientSecret,
                  callbackURL     : configAuth.facebookAuth.callbackURL,
                  passReqToCallback : true 
      
              },
      
              
              function(req, token, refreshToken, profile, done) {
      
                  
                  process.nextTick(function() {
      
                      
                      if (!req.user) {
      
                          
                          User.findOne({ 'facebook.id' : profile.id }, function(err, user) {
      
                              
                              
                              if (err)
                                  return done(err);
      
                              
                              if (user) {
      
                                  
                                  
                                  if (!user.facebook.token) {
                                      user.facebook.token = token;
                                      user.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName;
                                      user.facebook.email = profile.emails[0].value;
      
                                      user.save(function(err) {
                                          if (err)
                                              throw err;
                                          return done(null, user);
                                      });
                                  }
      
                                  return done(null, user); 
                              } else {
                                  
                                  var newUser            = new User();
      
                                  
                                  newUser.facebook.id    = profile.id; 
                                  newUser.facebook.token = token; 
                                  newUser.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName; 
                                  newUser.facebook.email = profile.emails[0].value; 
      
                                  
                                  newUser.save(function(err) {
                                      if (err)
                                          throw err;
      
                                      
                                      return done(null, newUser);
                                  });
                              }
      
                          });
      
                      } else {
                          
                          var user            = req.user; 
      
                          
                          user.facebook.id    = profile.id;
                          user.facebook.token = token;
                          user.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName;
                          user.facebook.email = profile.emails[0].value;
      
                          
                          user.save(function(err) {
                              if (err)
                                  throw err;
                              return done(null, user);
                          });
                      }
      
                  });
      
              }));
          ...
      

      Now just add that same code across the board to all of our Strategies and we have an application that can register a user, link accounts, unlink accounts, and relink accounts.

      Full Code

      For those interested in seeing the entire code altogether, make sure you check out the GitHub repo. Also, here are direct links to the two most important files:

      Hopefully, we covered most of the cases that you’ll run into when authenticating and authorizing users. Make sure to take a look at the full code and the demo to make sure that everything is working properly. If you see anything that raises questions, just let me know and be sure to go look at the full code for clarification!

      Thanks for sticking with us throughout this entire series. We hope you enjoyed it. We’ll be expanding on authentication further in the future by doing a Node and Angular authentication tutorial. Until then, happy authenticating!

      Next.js Authentication


      How to Join

      This Tech Talk is free and open to everyone. Register below to get a link to join the live stream or receive the video recording after it airs.

      DateTimeRSVP
      August 4, 202111:00 a.m.–12:00 p.m. ET / 3:00–4:00 p.m. GMT

      About the Talk

      Authentication is something that every application needs. We’ll add authentication to our Next.js application using the open source tool NextAuth.js.

      What You’ll Learn

      • Adding authentication to your Next.js application
      • Using Postgres as your database
      • Adding social authentication

      This Talk Is Designed For

      Frontend developers that want to authenticate your applications.

      Resources

      Intro to NextAuth.js



      Source link

      Leveraging MongoDB’s Built-in Authentication and Authorization Methods



      Part of the Series:
      MongoDB Security: Best Practices to Keep Your Data Safe

      MongoDB, also known as Mongo, is a document database used in many modern web applications. As with any database management system, it’s critical that those responsible for managing a Mongo database adhere to the recommended security best practices, both to prevent data from being lost in the event of a disaster and to keep it out of the hands of malicious actors.

      This series of conceptual articles provides a high-level overview of MongoDB’s built-in security features while also highlighting some general database security best practices.

      Authorization and authentication are two concepts that are critical for understanding database security. These two concepts are similar, but it’s important to understand what they are and what makes them different. Authentication is the process of confirming whether a user or client is actually who they claim to be. Authorization, on the other hand, involves setting rules for a given user or group of users to define what actions they can perform and which resources they can access.

      Authentication

      MongoDB features several mechanisms that allow you to authenticate users, with the default mechanism being its Salted Challenge Response Authentication Mechanism (SCRAM). SCRAM involves MongoDB reading and verifying credentials presented by a user against a combination of their username, password, and authentication database, all of which are known by the given MongoDB instance. If any of the user’s credentials don’t match what the Mongo database expects, the database won’t authenticate the user and they won’t gain access until they present the correct username, password, and authentication database.

      You can also use a text file to act as a shared password for a group of connected MongoDB instances, such as a replica set or shard cluster. This method, known as keyfile authentication, is considered to be a bare-minimum form of security and is best suited for testing or development environments, as advised by the MongoDB documentation.

      For production environments that implement sharding or replication, the MongoDB documentation recommends using another authentication mechanism: x.509 authentication. This involves distributing valid x.509 certificates — either self-signed or obtained from a third-party certificate authority — to the intended cluster members or clients. These are different from keyfiles, though, in that each machine gets its own dedicated x.509 certificate. This means that one machine’s certificate will only be useful for authenticating that machine. A client that presents a stolen x.509 certificate to the database server will not be able to authenticate.

      x.509 authentication leverages a concept known as mutual authentication. This means when a client or cluster member authenticates themself to the server, the server is likewise authenticating itself to the client or cluster member. If the client or cluster member attempts to connect to a database server with an invalid x.509 certificate, it will be prevented from doing so since the mutual authentication will fail.

      Authorization

      MongoDB manages authorization through a computer security concept known as role-based access control. Whenever you create a MongoDB user, you have the option to provide them with one or more roles. A role defines what privileges a user has, including what actions they can perform on a given database, collection, set of collections, or cluster. When you assign a role to a user, that user receives all the privileges of that role.

      MongoDB comes with a number of built-in roles that provide commonly-needed privileges. A few of these are available for every database, but most are only available for the admin database, as they’re intended to provide powerful administrative privileges. For example, you can assign a user the readWrite role on any database, meaning that you can read and modify the data held in any database on your system as long as you’ve granted a user the readWrite role over it. However, the readWriteAnyDatabase role — which allows the user to read and modify data on any database except for local and config — is only available in the admin database, as it provides broader system privileges.

      In addition to its built-in roles, Mongo also allows you to define custom roles, giving you even more control over what resources users can access on your system. Like users, roles are added in a specific database. Other than roles created in the admin database, which can include privileges to any database in the system, a user-defined role’s privileges only apply to the database in which the role was created. With that said, a role can include one or more existing roles in its definition, and a role can inherit privileges from other roles in the same database.

      With such fine-grained control over user privileges, you can set up dedicated users to perform certain functions, like a cluster administrator to manage replica sets and sharded clusters or a user administrator to create and manage users and custom roles. This type of user management strategy can also help harden your system’s security, as it reduces the number of users with broad privileges.



      Source link