📕
Dan Fitz's Notes
  • README
  • Ai
    • Supervised Machine Learning
      • Introduction To Machine Learning
      • Regression With Multiple Input Variables
      • Classification
  • Csharp
    • C Sharp Advanced
      • Generics
      • Delegates
      • Lambda Expressions
      • Events
    • C Sharp Fundamentals
      • Intro To C
      • Primitive Types And Expressions
      • Non Primitive Types
      • Control Flow
      • Arrays And Lists
      • Working With Dates
      • Working With Text
      • Working With Files
      • Debugging Applications
    • C Sharp Intermediate
      • Classes
      • Association Between Classes
      • Inheritance
      • Polymorphism
      • Interfaces
  • Java
    • Inheritance Data Structures Java
      • Inheritance Polymorphism Using Overriding And Access Modifiers
      • Abstract Classes And Debugging
      • File I O And Exceptions
      • Collections Maps And Regular Expressions
    • Intro To Java
      • Introduction To Java Classes And Eclipse
      • Unit Testing Arrays And Array Lists
      • Static Variables Methods And Polymorphism Using Overloading
  • Javascript
    • Algorithms Data Structures
      • Big O Notation
      • Analyzing Performance Of Arrays And Objects
      • Problem Solving Approach
      • Problem Solving Patterns
      • Recursion
      • Searching Algorithms
      • Bubble Selection And Insertion Sort
      • Merge Sort
      • Quick Sort
      • Radix Sort
      • Data Structures Introduction
      • Singly Linked Lists
      • Doubly Linked Lists
      • Stacks And Queues
      • Binary Search Trees
      • Tree Traversal
      • Binary Heaps
    • Complete Nodejs
      • Understanding Node.js
      • REST AP Is And Mongoose
      • API Authentication And Security
      • Node.js Module System
      • File System And Command Line Args
      • Debugging Node.js
      • Asynchronous Node.js
      • Web Servers
      • Accessing API From Browser
      • Application Deployment
      • Mongo DB And Promises
    • Complete React Native
      • Working With Content
      • Building Lists
      • Navigating Users Between Screens
      • State Management
      • Handling Screen Layout
      • Setting Up An App
      • More On Navigation
      • Advanced Statement Management With Context
      • Building A Custom Express API
      • In App Authentication
    • Epic React
      • React Fundamentals
      • React Hooks
      • Advanced React Hooks
      • Advanced React Patterns
      • React Performance
    • Fireship Firestore
      • Firestore Queries And Data Modeling Course
      • Model Relational Data In Firestore No SQL
    • Functional Light Javascript
      • Intro
      • Function Purity
      • Argument Adapters
      • Point Free
      • Closure
      • Composition
      • Immutability
      • Recursion
      • List Operations
      • Transduction
      • Data Structure Operations
      • Async
    • Js Weird Parts
      • Execution Contexts And Lexical Environments
      • Types And Operators
      • Objects And Functions
      • Object Oriented Java Script And Prototypal Inheritance
      • Defining Objects
    • Mastering Chrome Dev Tools
      • Introduction
      • Editing
      • Debugging
      • Networking
      • Auditing
      • Node.js Profiling
      • Performance Monitoring
      • Image Performance
      • Memory
    • React Complete Guide
      • What Is React
      • React Basics
      • Rendering Lists And Conditionals
      • Styling React Components
      • Debugging React Apps
      • Component Deep Dive
      • Building A React App
      • Reaching Out To The Web
      • Routing
    • React Testing
      • Intro To Jest Enzyme And TDD
      • Basic Testing
      • Redux Testing
      • Redux Thunk Testing
    • Serverless Bootcamp
      • Introduction
      • Auction Service Setup
      • Auction Service CRUD Operations
      • Auction Service Processing Auctions
    • Testing Javascript
      • Fundamentals Of Testing
      • Static Analysis Testing
      • Mocking Fundamentals
      • Configuring Jest
      • Test React Components With Jest And React Testing Library
    • Typescript Developers Guide
      • Getting Started With Type Script
      • What Is A Type System
      • Type Annotations In Action
      • Annotations With Functions And Objects
      • Mastering Typed Arrays
      • Tuples In Type Script
      • The All Important Interface
      • Building Functionality With Classes
    • Web Performance With Webpack
      • Intro
      • Code Splitting
      • Module Methods Magic Comments
  • Other
    • Algo Expert
      • Defining Data Structures And Complexity Analysis
      • Memory
      • Big O Notation
      • Logarithm
      • Arrays
      • Linked Lists
      • Hash Tables
      • Stacks And Queues
      • Strings
      • Graphs
      • Trees
    • Aws Solutions Architect
      • AWS Fundamentals IAM EC 2
    • Fundamentals Math
      • Numbers And Negative Numbers
      • Factors And Multiples
      • Fractions
    • Mysql Bootcamp
      • Overview And Installation
      • Creating Databases And Tables
      • Inserting Data
      • CRUD Commands
      • The World Of String Functions
      • Refining Our Selections
      • The Magic Of Aggregate Functions
    • Random Notes
      • Understanding React Hooks
  • Python
    • Data Analysis Using Python
      • Loading Querying And Filtering Data Using The Csv Module
      • Loading Querying Joining And Filtering Data Using Pandas
      • Summarizing And Visualizing Data
    • Intro To Python
      • Course Introduction Intro To Programming And The Python Language Variables Conditionals Jupyter Notebook And IDLE
      • Intro To Lists Loops And Functions
      • More With Lists Strings Tuples Sets And Py Charm
      • Dictionaries And Files
Powered by GitBook
On this page
  • Securely Storing Passwords
  • Hashing in model middleware
  • Logging in Users
  • Authentication Tokens
  • JSON web tokens
  • Generating, saving, and sending tokens
  • BONUS: How middleware work
  • Authenticating tokens
  • Logging out Users
  • Hiding Private Data
  • Authenticating User Endpoints
  1. Javascript
  2. Complete Nodejs

API Authentication And Security

The goal of this section is to learn how to lock down and secure an API that is currently open. That means securely storing passwords, setting up rules on what can and can't be read by a user, requiring them to authenticate themselves to access resources, etc.

Securely Storing Passwords

In a naive API and database, passwords are stored as plaintext. That means passwords are exposed. If someone were to get their hands on the data, they could now break into a user's accounts.

The solution we'll be implementing is to store hashed passwords: values algorithmically generated from passwords that are one-way, i.e., can't be reversed or decrypted. The hashing algorithm library we'll be using is bcryptjs:

const hashAndCompare = async () => {
  const password = 'mypassword'
  const hash = await bcrypt.hash(password, 8) // # of ROUNDS the algorithm goes through; more takes longer, less means less security

  const isMatch = await bcrypt.compare(password, hash)
}

Hashing in model middleware

Object modelers like Mongoose allow you to apply middleware that run before saving to the database. We can apply a hashing algorithm as middleware in a Mongoose schema:

const userSchema = mongoose.Schema(/* object */)

// Overwrites plaintext password with hashed password BEFORE saving
userSchema.pre('save', async function(next) {
  const user = this

  if (user.isModified('password')) {
    user.password = await bcrypt.hash(user.password, 8)
  }
  
  next()
})

Logging in Users

To log in users, we just need to

  1. Find the user by email, and

  2. Check if the given password matches the hashed password.

// We can wrap this logic in our own CUSTOM static model method
userSchema.statics.findByCredentials = async (email, password) => {
  // Find user
  const user = await User.findOne({ email })

  // If email not found...
  if (!user) throw new Error('Unable to login')

  // Check if password matches hash
  const isMatch = await bcrypt.compare(password, user.password)

  // If password doesn't match...
  if (!isMatch) throw new Error('Unable to login')

  return user
}

app.post('/login', async (req, res) => {
  try {
    const user = await User.findByCredentials(req.body.email, req.body.password)
    res.send(user)
  } catch (error) { // <= Catches 'Unable to login' messages
    res.status(400).send(error)
  }
})

Pro tip: When throwing error messages related to authentication, you want to keep it generic so as not to give away too much information.

Authentication Tokens

As you build out an API, your endpoints will fall into one of two types:

  • Public routes

    • Examples: login and sign-up

  • Routes that require authentication

    • Examples: viewing your own tweets, creating a post

The way to enforce authentication is to use an authentication token. When the user logs in, they're given a token to give them access to closed routes.

JSON web tokens

A popular type of authentication token is the JSON web token.

A JSON web token has 3 components:

  • Header information

    • Displays metadata like algorithm used or type of token

  • Data

    • Information you pass into the algorithm gets stored here

    • Note: Your token will have iat (issued-at timestamp) and could have exp (expiry timestamp)

  • Signature

    • This is a secret key used in the algorithm to verify that the token was made by the server

We'll use the jsonwebtoken library to implement our own JSON web tokens:

// Signature usually stored as environment variable
const secretSignature = process.env.SECRET_SIGNATURE

const payload = {}

// Creating token
const token = jwt.sign(
  payload,
  secretSignature,
  { expiresIn: '7 days' } // options
)

// Verifying token
// Returns data OR throws error
const returnedData = jwt.verify(token, secretSignature)

Generating, saving, and sending tokens

When a user logs in, you need to do the following:

  1. Generate the token.

  2. Save it in the database associated with the user.

  3. Send the token back in the HTTP response.

Here's the code for steps 1, 2, and 3:

// In model...
userSchema.methods.generateAuthToken = async function() {
  const user = this
  
  const token = await jwt.sign(
    { _id: user._id.toString() }, // payload
    'secret',
    { expiresIn: '1 day' } // options
  )

  // Adds a new token doc into tokens subcollection
  user.tokens = user.tokens.concat({ token })
  await user.save()

  return token
}

// In router...
app.post('/login', async (req, res) => {
 const user = await User.findByCredentials(req.body.email, req.body.password)
 const token = await user.generateAuthToken()
 res.send({ user, token })
})

BONUS: How middleware work

As a preface to the next heading, we'll explain how middleware work in express:

A middleware is basically a function that gets called between when a new request is made and when a route handler processes and responds to it. It has access to req and res as normal, and it has a next function that tells express to move onto the next middleware or the route handler.

// Stops short all GET requests, instead sending custom response message
app.use((req, res, next) => {
  if (req.method === 'GET') {
    res.status(400).send('GET requests are disabled')
  } else {
    next()
  }
})

Middleware are a great place to control the flow of your routes and to implement checks before handling a request--like authentication!

Authenticating tokens

When a user makes a request to a route that requires authentication, a smart design choice is to use a middleware to gatekeep your routes:

  1. Validate that the token provided can be found in the database.

  2. If the token is good, move onto the route handler.

  3. Finally, pass the middleware to the routes that you want to lock down via authentication.

In the request header, the user must send the token they were issued.

Authorization: Bearer 3202j3gj32j2309fj.230932jf2.23f092j3f

Note: A good idea when creating your tokens is to include the user's _id in the payload. That way you can use the _id to query for the token within the user's data--like this:

const authMiddleware = async (req, res, next) => {
  // Get token from header
  const token = req.header('Authorization').replace('Bearer ', '')

  // Decode payload
  const decoded = jwt.verify(token, 'secret')

  // Find user using decoded._id AND token
  const user = await User.findOne({ _id: decoded._id, 'tokens.token': token })

  // Attach user and token to request, allowing route handler to access user info
  req.user = user
  req.token = token
  next()
})

// In route file, pass auth middleware to route
router.get('/profile', authMiddleware, (req, res) => {
  // This only runs IF authentication succeeds
})

Logging out Users

Because our endpoints can include an authMiddleware that passes user and token information, setting up a logout endpoint is a breeze:

  1. Simply remove the token from tokens, then

  2. Save and send 200 response.

app.post('/logout', authMiddleware, async (req, res) => {
  try {
    req.user.tokens = req.user.tokens(token => token.token !== req.token)
    await req.user.save()
    res.status(200).send()
  } catch (error) {
    res.status(500).send()
  }
})

Hiding Private Data

In our naive endpoints setup, any query to user information automatically returns everything, including password and tokens.

One solution is to implement a getPublicProfile instance method:

// In model user.js
userSchema.methods.getPublicProfile = function() {
  const user = this
  const userObject = user.toObject() // removes methods
  
  // Strips away private data
  delete userObject.password
  delete userObject.tokens

  return userObject
}

// In endpoint
app.get('/users/:id', async (req, res) => {
  const user = await User.findById(req.params.id)
  res.send(user.getPublicProfile())
})

Pro tip: Whenever you send a response, express automatically serializes the object into JSON using JSON.stringify. However, you can override what gets serialized by including a toJSON method in the object. By doing this, the return value of toJSON is instead serialized.

Here's a simple example:

const person = {
  name: 'Dan',
  age: 27,
  toJSON: () => ({ message: 'I get serialized instead' })
}

console.log(JSON.stringify(person))
// Logs { "message": "I get serialized instead" }

Applied to removing private data...

// In model user.js
userSchema.methods.toJSON = function() {
  // Same stuff...
}

// In endpoint
app.get('/users/:id', async (req, res) => {
  const user = User.findById(req.params.id)
  res.send(user) // AUTOMATICALLY removes private data now
})

Authenticating User Endpoints

PreviousREST AP Is And MongooseNextNode.js Module System

Last updated 3 years ago