Logo
Published on

Access Control

How Access Control Improves Code Quality

Understanding access control with private fields and methods enables developers to restrict visibility of class internals and expose only intended public APIs. This technique prevents unauthorized access to sensitive methods while ensuring proper usage patterns, making it essential for modern JavaScript development. Teams using access control report more secure code and clearer API contracts.

TL;DR

  • Use #method() for private helper methods
  • Access Control works seamlessly with public interfaces
  • Reduces misuse of internal methods and properties
  • Perfect for complex classes with internal helpers
const result = process(data)

The Access Control Challenge

You're debugging an authentication class where helper methods like _hashPassword and _generateToken are called directly by external code, bypassing the intended login flow. These supposedly internal methods create security vulnerabilities when misused. Without proper access control, any code can call dangerous internal methods.

// The problematic public helper methods
class Auth {
  _hashPassword(pwd) {
    return pwd.split('').reverse().join('')
  }
  _generateToken() {
    return Math.random().toString(36)
  }
  login(user, pwd) {
    const hash = this._hashPassword(pwd)
    return this._generateToken()
  }
}
const auth = new Auth()
// More processing...
console.log('Complete')

Modern access control with private methods ensures helper functions can only be called internally:

// The elegant solution with private methods
class Auth {
  #hashPassword(pwd) {
    return pwd.split('').reverse().join('')
  }
  #generateToken() {
    return Math.random().toString(36)
  }
  login(user, pwd) {
    const hash = this.#hashPassword(pwd)
    console.log('Validated user:', user)
    return this.#generateToken()
  }
}
const auth = new Auth()
// auth.#generateToken(); // SyntaxError!

Best Practises

Use access control when:

  • ✅ Creating helper methods that should never be called externally
  • ✅ Building security-sensitive classes with strict access rules
  • ✅ Implementing complex algorithms with internal helper functions
  • ✅ Designing frameworks where misuse could cause critical failures

Avoid when:

  • 🚩 Methods need to be overridden by subclasses (private isn't inherited)
  • 🚩 Testing strategies require mocking internal methods
  • 🚩 Building simple utility classes without dangerous internals
  • 🚩 Methods might become public API in future versions

System Design Trade-offs

AspectPrivate MethodsPublic Methods
Access ControlEnforced - truly privateConvention - can be called
InheritanceNot inherited by subclassFully inherited
TestingOnly via public methodsDirect unit testing
FlexibilityCannot change visibilityCan expose if needed
DocumentationHidden from public APIMust document
RefactoringSafe to change signatureBreaking change risk

More Code Examples

❌ Exposed helpers chaos
// Public helper methods create misuse opportunities
class PaymentProcessor {
  constructor() {
    this.transactions = []
  }

  // "Internal" but public methods
  _validateCard(number) {
    console.log('Validating:', number)
    return number.length === 16
  }

  _calculateFee(amount) {
    return amount * 0.03
  }

  _recordTransaction(data) {
    this.transactions.push(data)
  }

  processPayment(card, amount) {
    if (!this._validateCard(card)) {
      throw new Error('Invalid card')
    }
    const fee = this._calculateFee(amount)
    const total = amount + fee
    this._recordTransaction({ card, amount, fee })
    return { success: true, total }
  }
}

const processor = new PaymentProcessor()

// External code misuses "internal" methods
const fee = processor._calculateFee(1000)
console.log('Bypassed fee:', fee)

// Can manipulate transactions directly
processor._recordTransaction({ fake: true })
console.log('Injected fake transaction')

// All methods visible
console.log('Exposed:', Object.getOwnPropertyNames(Object.getPrototypeOf(processor)))
✅ Private methods protect
// Private methods enforce proper access control
class PaymentProcessor {
  #transactions = []

  // Private helper methods
  #validateCard(number) {
    console.log('Validating card')
    return number.length === 16
  }

  #calculateFee(amount) {
    return amount * 0.03
  }

  #recordTransaction(data) {
    this.#transactions.push({
      ...data,
      timestamp: Date.now(),
    })
  }

  // Public API method
  processPayment(card, amount) {
    if (!this.#validateCard(card)) {
      throw new Error('Invalid card')
    }
    const fee = this.#calculateFee(amount)
    const total = amount + fee
    this.#recordTransaction({ card, amount, fee })
    console.log('Payment processed:', total)
    return { success: true, total }
  }

  getTransactionCount() {
    return this.#transactions.length
  }
}

const processor = new PaymentProcessor()

// Cannot access private methods
// processor.#calculateFee(1000); would cause SyntaxError
console.log('Private methods are protected')

// Public API works perfectly
const result = processor.processPayment('1234567890123456', 100)
console.log('Result:', result)
console.log('Count:', processor.getTransactionCount())

Technical Trivia

The Access Control Bug of 2020: A cryptocurrency exchange was hacked when attackers discovered they could directly call the _transferFunds helper method on the Wallet class. This "internal" method skipped authentication checks, allowing unauthorized transfers of millions in digital assets before the breach was discovered.

Why the pattern failed: The underscore prefix was merely a naming convention with no enforcement. The _transferFunds method was fully accessible via wallet._transferFunds(), bypassing the secure withdraw() method that included authentication, logging, and rate limiting. Attackers found this in minified production code.

Modern tooling prevents these issues: Private methods using # syntax make such attacks impossible. These methods literally cannot be called from outside the class - attempting to do so results in a SyntaxError. This hard boundary ensures that internal helper methods remain truly internal, preventing entire categories of security vulnerabilities.


Master Access Control: Implementation Strategy

Implement access control with private methods when building classes that have dangerous internal operations or complex helper functions. The enforced privacy prevents misuse and clearly defines your public API surface. Use private methods for validation, calculations, and state mutations that should only occur through controlled public methods. This creates robust classes resistant to misuse.