Authentication vs Authorization

Understanding the Foundation of Web Application Security

Introduction

In the world of web development and application security, two terms are frequently used but often confused: authentication and authorization. Understanding the difference between these concepts is crucial for building secure applications that protect user data and resources. Today, we'll explore these concepts in depth, see how they work together, and learn how to implement them in our full-stack JavaScript applications.

flowchart TD A[Web Application Security] --> B[Authentication] A --> C[Authorization] B --> D[Who are you?] C --> E[What can you do?] style A fill:#f9f9f9,stroke:#333,stroke-width:2px style B fill:#d1e7dd,stroke:#0f5132,stroke-width:2px style C fill:#cfe2ff,stroke:#084298,stroke-width:2px style D fill:#d1e7dd,stroke:#0f5132,stroke-width:1px style E fill:#cfe2ff,stroke:#084298,stroke-width:1px

Think of authentication and authorization as the two security guards at a gated community. Authentication is the guard at the entrance who checks your ID to verify you are who you claim to be. Authorization is the second guard who, once you're inside, determines which areas you're allowed to access based on your resident status, whether you're a homeowner, a guest, or maintenance staff.

Authentication: Proving Identity

Authentication is the process of verifying who a user is. It answers the question: "Are you who you say you are?" Authentication is about identity verification.

Authentication Methods

Real-World Authentication Example

When you visit a bank, you might need to show your ID (something you have) and perhaps enter a PIN at the ATM (something you know). For high-security operations, you might also need to provide a fingerprint (something you are). This combination of factors increases the security of the authentication process.

Common Authentication Flows in Web Applications

sequenceDiagram participant User participant Client as Client (Browser) participant Server participant Database User->>Client: Enter credentials Client->>Server: Send login request Server->>Database: Verify credentials Database-->>Server: Credentials valid/invalid alt Credentials Valid Server-->>Client: Generate & send token/session Client-->>User: Display authenticated UI else Credentials Invalid Server-->>Client: Send error Client-->>User: Display error message end

Common Authentication Methods in JavaScript Applications

  1. Session-Based Authentication: Server maintains user state using sessions
    • Server generates a session ID and stores user data
    • Client stores the session ID in a cookie
    • Stateful: Server needs to store session information
  2. Token-Based Authentication (JWT): Stateless authentication using tokens
    • Server generates a signed token containing user data
    • Client stores the token (e.g., in localStorage)
    • Stateless: Server doesn't need to store session information
  3. OAuth: Authentication delegated to a trusted third party
    • "Log in with Google/Facebook/GitHub"
    • Provides access to resources without sharing credentials

Authentication Code Example: Node.js and Express

Here's a simple example of implementing password-based authentication with Express and MongoDB:

User Model (models/User.js)


const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

const UserSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, 'Please add a name'],
  },
  email: {
    type: String,
    required: [true, 'Please add an email'],
    unique: true,
    match: [
      /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/,
      'Please add a valid email',
    ],
  },
  password: {
    type: String,
    required: [true, 'Please add a password'],
    minlength: 6,
    select: false, // Don't return password in queries
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

// Encrypt password using bcrypt
UserSchema.pre('save', async function (next) {
  if (!this.isModified('password')) {
    next();
  }

  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
});

// Sign JWT and return
UserSchema.methods.getSignedJwtToken = function () {
  return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRE,
  });
};

// Match user entered password to hashed password in database
UserSchema.methods.matchPassword = async function (enteredPassword) {
  return await bcrypt.compare(enteredPassword, this.password);
};

module.exports = mongoose.model('User', UserSchema);
                

Authentication Controller (controllers/auth.js)


const User = require('../models/User');
const ErrorResponse = require('../utils/errorResponse');
const asyncHandler = require('../middleware/async');

// @desc    Register user
// @route   POST /api/v1/auth/register
// @access  Public
exports.register = asyncHandler(async (req, res, next) => {
  const { name, email, password } = req.body;

  // Create user
  const user = await User.create({
    name,
    email,
    password,
  });

  sendTokenResponse(user, 200, res);
});

// @desc    Login user
// @route   POST /api/v1/auth/login
// @access  Public
exports.login = asyncHandler(async (req, res, next) => {
  const { email, password } = req.body;

  // Validate email & password
  if (!email || !password) {
    return next(new ErrorResponse('Please provide an email and password', 400));
  }

  // Check for user
  const user = await User.findOne({ email }).select('+password');

  if (!user) {
    return next(new ErrorResponse('Invalid credentials', 401));
  }

  // Check if password matches
  const isMatch = await user.matchPassword(password);

  if (!isMatch) {
    return next(new ErrorResponse('Invalid credentials', 401));
  }

  sendTokenResponse(user, 200, res);
});

// Get token from model, create cookie and send response
const sendTokenResponse = (user, statusCode, res) => {
  // Create token
  const token = user.getSignedJwtToken();

  const options = {
    expires: new Date(
      Date.now() + process.env.JWT_COOKIE_EXPIRE * 24 * 60 * 60 * 1000
    ),
    httpOnly: true,
  };

  if (process.env.NODE_ENV === 'production') {
    options.secure = true;
  }

  res
    .status(statusCode)
    .cookie('token', token, options)
    .json({
      success: true,
      token,
    });
};
                

Authentication Routes (routes/auth.js)


const express = require('express');
const { register, login } = require('../controllers/auth');

const router = express.Router();

router.post('/register', register);
router.post('/login', login);

module.exports = router;
                

Analogy: Authentication as an Airport Check-In

Think of authentication like checking in at an airport. When you arrive, you present your ID and boarding pass at security. The security officer compares your face to your ID photo and verifies your boarding pass details. This process confirms you are who you claim to be and that you have a valid ticket for travel. Once authenticated, you receive a boarding pass – similar to a token or session in web applications – that proves you've been vetted and can proceed to the gate areas.

Authorization: Granting Permissions

Authorization is the process of determining what a user can access or do. It answers the question: "Are you allowed to do/see this?" Authorization is about permissions and access control.

While authentication is about identity verification, authorization is about access control. Once a user is authenticated, the system then needs to determine what that user is allowed to do.

Authorization Models

graph TD A[Authorization Models] --> B[Role-Based Access Control] A --> C[Attribute-Based Access Control] A --> D[Access Control Lists] A --> E[Policy-Based Access Control] B --> B1[Admin Role] B --> B2[Editor Role] B --> B3[User Role] B1 --> B1a[Full Access] B2 --> B2a[Create/Edit Content] B2 --> B2b[Moderate Comments] B3 --> B3a[Read Content] B3 --> B3b[Submit Comments] style A fill:#f9f9f9,stroke:#333,stroke-width:2px style B fill:#cfe2ff,stroke:#084298,stroke-width:2px style C fill:#cfe2ff,stroke:#084298,stroke-width:2px style D fill:#cfe2ff,stroke:#084298,stroke-width:2px style E fill:#cfe2ff,stroke:#084298,stroke-width:2px

Real-World Authorization Example

In a company, different employees have different access levels to various systems:

  • A sales representative can access the CRM system but can't view financial reports
  • A department manager can approve time-off requests for their team members but not for other departments
  • An IT administrator has access to system configurations that regular employees don't

These access levels are all examples of authorization – determining what actions a verified user is permitted to perform.

Authorization Code Example: Middleware in Express

Here's an example of implementing role-based authorization middleware in Express:

Auth Middleware (middleware/auth.js)


const jwt = require('jsonwebtoken');
const asyncHandler = require('./async');
const ErrorResponse = require('../utils/errorResponse');
const User = require('../models/User');

// Protect routes
exports.protect = asyncHandler(async (req, res, next) => {
  let token;

  if (
    req.headers.authorization &&
    req.headers.authorization.startsWith('Bearer')
  ) {
    // Extract token from Bearer token in header
    token = req.headers.authorization.split(' ')[1];
  } else if (req.cookies.token) {
    // Extract token from cookie
    token = req.cookies.token;
  }

  // Make sure token exists
  if (!token) {
    return next(new ErrorResponse('Not authorized to access this route', 401));
  }

  try {
    // Verify token
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    // Add user to request object
    req.user = await User.findById(decoded.id);

    next();
  } catch (err) {
    return next(new ErrorResponse('Not authorized to access this route', 401));
  }
});

// Grant access to specific roles
exports.authorize = (...roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return next(
        new ErrorResponse(
          `User role ${req.user.role} is not authorized to access this route`,
          403
        )
      );
    }
    next();
  };
};
                

Using the Authorization Middleware in Routes


const express = require('express');
const {
  getUsers,
  getUser,
  createUser,
  updateUser,
  deleteUser
} = require('../controllers/users');

const router = express.Router();

const { protect, authorize } = require('../middleware/auth');

// Apply protect middleware to all routes below
router.use(protect);

// Only admins can access these routes
router
  .route('/')
  .get(authorize('admin'), getUsers)
  .post(authorize('admin'), createUser);

router
  .route('/:id')
  .get(authorize('admin'), getUser)
  .put(authorize('admin'), updateUser)
  .delete(authorize('admin'), deleteUser);

module.exports = router;
                

Analogy: Authorization as Hotel Key Cards

Imagine a hotel where everyone receives a key card upon check-in. This key card is your authentication – it proves you're a verified guest. However, not all key cards work the same way:

  • Standard guest key cards only open your specific room
  • VIP guest key cards provide access to exclusive lounges and facilities
  • Staff key cards access service areas but not guest rooms
  • Manager key cards can access almost anywhere in the hotel

The key card authenticates you as someone who belongs in the hotel, but the specific permissions encoded on it determine what doors you can open. That's authorization.

Authentication vs Authorization: Key Differences

Aspect Authentication Authorization
Purpose Verifies who the user is Determines what resources a user can access
Question Answered "Who are you?" "What can you access?"
Process Validates user credentials Checks user permissions
Timing Done before authorization Done after successful authentication
Data Input User ID, password, biometrics, etc. User roles, policies, ACLs
Common Methods Passwords, 2FA, social login RBAC, ABAC, JWT claims
Error Messages "Invalid credentials" "Insufficient permissions"
HTTP Status Codes 401 Unauthorized 403 Forbidden

Important Note on HTTP Status Codes

Despite its name, the HTTP 401 status code is actually for authentication errors, not authorization. The naming is confusing and a common source of misunderstanding:

  • 401 Unauthorized: Should be used when authentication fails (user is unknown)
  • 403 Forbidden: Should be used when authorization fails (user is known but doesn't have permission)
sequenceDiagram participant User participant Auth as Authentication Layer participant Authz as Authorization Layer participant Resource as Protected Resource User->>Auth: Present credentials alt Invalid Credentials Auth-->>User: 401 Unauthorized else Valid Credentials Auth->>Authz: Forward authenticated user alt Insufficient Permissions Authz-->>User: 403 Forbidden else Sufficient Permissions Authz->>Resource: Allow access Resource-->>User: 200 OK with requested data end end

How Authentication and Authorization Work Together

Authentication and authorization work hand in hand to create a complete security system. Here's a typical flow in a web application:

  1. User Identification: User provides credentials (authentication data)
  2. Verification: System verifies the credentials against stored data
  3. Session/Token Creation: Upon successful authentication, the system creates a session or token
  4. Authorization Check: When the user tries to access a resource, the system checks if they have permission
  5. Access Control: Based on authorization rules, the system grants or denies access
Authentication Authorization Login with credentials Verify identity Issue token/session Check user role/permissions Verify resource access rights Grant or deny access

Full Implementation Example: Protected API Routes

Let's see a complete example of implementing both authentication and authorization in a Node.js/Express API:

Server Setup (server.js)


const express = require('express');
const dotenv = require('dotenv');
const cookieParser = require('cookie-parser');
const connectDB = require('./config/db');

// Load environment variables
dotenv.config({ path: './config/config.env' });

// Connect to database
connectDB();

// Route files
const auth = require('./routes/auth');
const posts = require('./routes/posts');

const app = express();

// Body parser
app.use(express.json());

// Cookie parser
app.use(cookieParser());

// Mount routers
app.use('/api/v1/auth', auth);
app.use('/api/v1/posts', posts);

const PORT = process.env.PORT || 5000;

app.listen(PORT, console.log(`Server running on port ${PORT}`));
                

Post Model with Ownership (models/Post.js)


const mongoose = require('mongoose');

const PostSchema = new mongoose.Schema({
  title: {
    type: String,
    required: [true, 'Please add a title'],
    trim: true,
    maxlength: [100, 'Title cannot be more than 100 characters']
  },
  content: {
    type: String,
    required: [true, 'Please add content']
  },
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

module.exports = mongoose.model('Post', PostSchema);
                

Post Controller with Authorization Checks (controllers/posts.js)


const Post = require('../models/Post');
const ErrorResponse = require('../utils/errorResponse');
const asyncHandler = require('../middleware/async');

// @desc    Get all posts
// @route   GET /api/v1/posts
// @access  Public
exports.getPosts = asyncHandler(async (req, res, next) => {
  const posts = await Post.find().populate('user', 'name');

  res.status(200).json({
    success: true,
    count: posts.length,
    data: posts
  });
});

// @desc    Get single post
// @route   GET /api/v1/posts/:id
// @access  Public
exports.getPost = asyncHandler(async (req, res, next) => {
  const post = await Post.findById(req.params.id).populate('user', 'name');

  if (!post) {
    return next(
      new ErrorResponse(`Post not found with id of ${req.params.id}`, 404)
    );
  }

  res.status(200).json({
    success: true,
    data: post
  });
});

// @desc    Create new post
// @route   POST /api/v1/posts
// @access  Private
exports.createPost = asyncHandler(async (req, res, next) => {
  // Add user to req.body
  req.body.user = req.user.id;

  const post = await Post.create(req.body);

  res.status(201).json({
    success: true,
    data: post
  });
});

// @desc    Update post
// @route   PUT /api/v1/posts/:id
// @access  Private
exports.updatePost = asyncHandler(async (req, res, next) => {
  let post = await Post.findById(req.params.id);

  if (!post) {
    return next(
      new ErrorResponse(`Post not found with id of ${req.params.id}`, 404)
    );
  }

  // Make sure user is post owner or admin
  if (post.user.toString() !== req.user.id && req.user.role !== 'admin') {
    return next(
      new ErrorResponse(
        `User ${req.user.id} is not authorized to update this post`,
        403
      )
    );
  }

  post = await Post.findByIdAndUpdate(req.params.id, req.body, {
    new: true,
    runValidators: true
  });

  res.status(200).json({
    success: true,
    data: post
  });
});

// @desc    Delete post
// @route   DELETE /api/v1/posts/:id
// @access  Private
exports.deletePost = asyncHandler(async (req, res, next) => {
  const post = await Post.findById(req.params.id);

  if (!post) {
    return next(
      new ErrorResponse(`Post not found with id of ${req.params.id}`, 404)
    );
  }

  // Make sure user is post owner or admin
  if (post.user.toString() !== req.user.id && req.user.role !== 'admin') {
    return next(
      new ErrorResponse(
        `User ${req.user.id} is not authorized to delete this post`,
        403
      )
    );
  }

  await post.remove();

  res.status(200).json({
    success: true,
    data: {}
  });
});
                

Post Routes with Auth Middleware (routes/posts.js)


const express = require('express');
const {
  getPosts,
  getPost,
  createPost,
  updatePost,
  deletePost
} = require('../controllers/posts');

const router = express.Router();

const { protect } = require('../middleware/auth');

router
  .route('/')
  .get(getPosts)
  .post(protect, createPost);

router
  .route('/:id')
  .get(getPost)
  .put(protect, updatePost)
  .delete(protect, deletePost);

module.exports = router;
                

In this example:

Best Practices for Authentication and Authorization

Authentication Best Practices

Authorization Best Practices

Common Security Pitfalls to Avoid

  • Insecure Direct Object References (IDOR): Directly exposing database IDs without authorization checks
  • Missing Function Level Access Control: Implementing access controls on the UI but not on API endpoints
  • Cross-Site Request Forgery (CSRF): Not protecting state-changing operations from forged requests
  • JWT Security Issues: Using weak secrets, not validating signatures, or storing sensitive data in JWTs
  • Session Fixation: Not regenerating session IDs after authentication
  • Improper Error Handling: Leaking sensitive information in error messages

Real-World Applications

Authentication and Authorization in Modern Web Applications

Understanding authentication and authorization is crucial for many real-world scenarios:

E-commerce Platforms

  • Authentication: User accounts, guest checkout options, saved payment methods
  • Authorization: Customer vs seller permissions, order history access, admin dashboards

Social Media Platforms

  • Authentication: Social logins, 2FA for account protection
  • Authorization: Privacy settings, content visibility, feature access (like verified badges)

SaaS Applications

  • Authentication: SSO, organization-wide logins
  • Authorization: Subscription-based feature access, team hierarchies, admin consoles

Banking Applications

  • Authentication: Strict multi-factor, biometric verification
  • Authorization: Transaction limits, account access levels, approval workflows

Analogy: Nightclub Security System

Imagine a popular nightclub with different areas: the main floor, VIP lounge, and exclusive private rooms. The club's security system works like this:

  • Authentication is managed by bouncers at the entrance who check IDs to verify patrons are who they claim to be and are of legal age. Some special guests might need additional verification, like having their names on a guest list (similar to 2FA).
  • Authorization is handled through colored wristbands issued after authentication:
    • Regular patrons get a basic wristband (standard user role)
    • VIP guests get a gold wristband (premium user role)
    • Celebrities get a black wristband (special access role)
    • Staff members get specific badges based on their job function (role-based access)

Inside the club, security staff check these wristbands before allowing access to different areas – just like an application checks permissions before allowing access to different features or resources.

Practice Activities

Activity 1: Implement Basic Authentication

Create a simple Express application with user registration and login functionality. Use bcrypt for password hashing and JWT for token generation.

  1. Create a User model with username, email, and password fields
  2. Implement routes for registration and login
  3. Add password hashing with bcrypt
  4. Generate and return JWTs on successful authentication

Activity 2: Implement Role-Based Authorization

Extend the application from Activity 1 to include role-based authorization.

  1. Add a role field to the User model with values like 'user', 'editor', and 'admin'
  2. Create a protected resource (e.g., an API endpoint) that requires authentication
  3. Implement middleware to check if the authenticated user has the required role
  4. Create routes that are accessible to different roles

Activity 3: Implement Object-Level Authorization

Implement object-level authorization for a resource like blog posts or comments.

  1. Create a Post model with a reference to the User model (the author)
  2. Implement CRUD operations for posts
  3. Ensure users can only edit or delete their own posts (unless they're admins)
  4. Test the authorization with different user accounts

Activity 4: Security Audit

Perform a security audit on your authentication and authorization implementation:

  1. Check for common security vulnerabilities (e.g., IDOR, XSS, CSRF)
  2. Ensure you're following all the best practices mentioned in this lecture
  3. Add appropriate error handling and logging
  4. Implement rate limiting on authentication endpoints

Conclusion

Authentication and authorization are foundational concepts in web application security. While often confused, they serve distinct purposes:

Implementing both correctly is essential for building secure applications. Authentication ensures only legitimate users can access your system, while authorization ensures they can only perform actions and access resources appropriate to their role or permissions.

As you build more complex applications, you'll encounter various authentication strategies (session-based, JWT, OAuth) and authorization models (RBAC, ABAC, ACL). The principles we've discussed today will help you understand and implement these systems effectively.

Remember: security is about defense in depth. Neither authentication nor authorization alone is sufficient – you need both, implemented properly, to create a secure application.

Additional Resources