”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 前端和后端开发的关键安全原则

前端和后端开发的关键安全原则

发布于2024-11-03
浏览:134

Key Security principles for Frontend and backend Development

Introduction:

Building a full-stack application is a rewarding challenge, but it's crucial to prioritize security from the very start. Have you considered the essential security principles needed to protect your product? How can we ensure that the application meets security standards effectively, and how easy is it to implement these principles?

In this article, I will explore fundamental security principles for both frontend and backend development. We’ll also discuss practical steps to implement them and ensure that your app adheres to industry security standards.

  1. Securing the frontend:
  • Input Validation and Sanitization:

-Ensure that all input from users is properly validated and sanitized to avoid injection attacks such as Cross-Site Scripting (XSS).

-Use libraries like DOMPurify for sanitizing user inputs that will be inserted into the DOM.

  • Authentication and Authorization:

-Implement secure authentication methods such as OAuth or the famous JWT method (JSON Web Tokens) to ensure that users are who they claim to be.
-Use role-based access control (RBAC) to restrict parts of the UI based on user permissions, for example part of the UI reserved to admin and other part for ordinary users.

// the code below show an example of how user 
//should be authenticated and depending 
//on his role the dashboard UI display
const createRoutes = (logged: boolean, role:string) =>
  createBrowserRouter([
{
          path: "/dashboard",
          loader: combinedDashboardLoader,

          element: (
            
              {role==="admin"}?:
            
          ),
          errorElement: ,
        }])

  • Secure Storage of sensitive Data:

-Avoid storing sensitive information like access tokens in localStorage or sessionStorage as they are vulnerable to XSS. Instead, use secure cookies with the HttpOnly and Secure flags enabled.

// in your server code you can write this code
//  inside your login controller function
// so you don't need to send your token to the frontend
res.cookie("accessToken", accessToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      maxAge: expirationTime * 1000,
      sameSite: "lax",
    });

  • CORS (Cross-Origin Resource Sharing):

-Implement proper CORS policies to only allow specific origins to interact with your backend API. Ensure that you restrict methods and headers that are allowed.

  • Content Security Policy (CSP):

-Set up a robust CSP to prevent the execution of untrusted scripts on your frontend. This reduces the risk of XSS attacks by controlling where resources can be loaded from.
-_Implementation _:
the easiest way is to simply use meta tags in your head of the html page as shown below in the code , for more details you can refer to this link https://www.stackhawk.com/blog/react-content-security-policy-guide-what-it-is-and-how-to-enable-it/

  
    ...
    
  

-Alternatively you can a middleware in your EpressJS as below:

app.use((req, res, next) => {
  res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none';");
  next();
});

  • HTTPS:

Enforce HTTPS to encrypt communication between the client and the server, ensuring that data is not exposed during transit.

  1. Securing the backend:
  • Input Validation and Sanitization (Server-side):

-Always validate and sanitize user inputs on the server side to prevent SQL injection or other code injection attacks. Even if input validation is done on the frontend, it should always be performed on the backend as well.
below an example using express validator middleware, then simply apply this middleware before your registration controller function.

import { body, validationResult } from "express-validator";
const validateUserInput = [
    body("email").isEmail().withMessage("Please enter a valid email address"),
    body("name")
        .isLength({ min: 3 })
        .withMessage("Name must be at least 3 characters long"),
    body("password")
        .isLength({ min: 6 })
        .withMessage("Password must be longer than 5 characters"),

    // Middleware to check for validation errors
    (req, res, next) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).json({ errors: errors.array()[0].msg });
        }
        next();
    },
];
export default validateUserInput;
//in your router file apply it 
//register is the post request function to add new user
router.route("/register").post(validateUserInput, register);

  • Authentication and Authorization:

-Use robust authentication mechanisms, such as OAuth, and secure tokens like JWT for session management.

-Implement role-based access control (RBAC) and avoid hardcoding permissions directly in your application.

  • Password security:

-Store passwords securely using strong hashing algorithms like bcrypt or Argon2. Never store plain text passwords.

-If needed implement multi-factor authentication (MFA) for an additional layer of security.

the code below shows how the process of securing password and using JWT (authentication).

// hashing password inside your registration function  
const register = async (req: Request, res: Response) => {
  const { email, password, name } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
createUser(email,name,hashedpassword) //a function to add new user into your DB
}

// Compare the password to the hashed one during the login request
const login = async (req: Request, res: Response) => {
  const { email, password } = req.body;
const existingUser = await query(findUserByEmail, [email]);
    if (!existingUser[0])
      return res.status(404).json({
        message: `${email} not found! register if you don't have an account`,
      });
    const matchPassword = await bcrypt.compare(
      password,
      existingUser[0].password
    );
//generate token JWT
    const expirationTime = 180;
    const accessToken = jwt.sign(
      { id: existingUser[0].iduser },
      process.env.ACCESS_TOKEN_SECRET!,
      {
        expiresIn: expirationTime   "s",
      }
    );}

  • Rate Limiting and Throttling: To prevent brute-force attacks, implement rate limiting and IP-based throttling on sensitive endpoints such as login routes.

Rate Limiting:
It restricts the number of requests a user (identified by IP, API key, etc.) can make to the server in a given time window (e.g., 100 requests per minute). After the limit is reached, further requests are blocked or delayed.

const express = require('express');
const rateLimit = require('express-rate-limit');
const app = express();

// Define rate limiting rule: 100 requests per 15 minutes
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests 
  message: 'Too many requests from this IP, please try again later.', // Custom message 
  standardHeaders: true,  `RateLimit-*` headers
  legacyHeaders: false, 
});

// Apply the rate limiting middleware to all requests

app.use(limiter);

//but we can also apply it to a specific route 
app.post('/login', limiter, (req, res) => {
  res.send('Login route');
});


app.listen(3000, () => {
  console.log('Server is running on port 3000');
});


Throttling:
Similar to rate limiting, but instead of blocking requests entirely, it slows down the processing of requests after a certain threshold is reached. This can avoid service overloads.

Example:
If a user makes more than 10 API requests per second, you slow down the response to every subsequent request to ensure the system isn’t overwhelmed.

  • Data encryption:

Use encryption for sensitive data both at rest and in transit. Ensure that databases and other storage mechanisms encrypt sensitive information.

the code below shows an implementation of data encryption of a credit card number before storing it in the database.

const crypto = require('crypto');

// Encryption settings
const encryptionKey = crypto.randomBytes(32); // AES-256 key
const iv = crypto.randomBytes(16); // Initialization vector

// Encrypt function
function encrypt(text) {
  const cipher = crypto.createCipheriv('aes-256-cbc', encryptionKey, iv);
  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted  = cipher.final('hex');
  return `${iv.toString('hex')}:${encrypted}`; // Store both IV and encrypted text
}

// Post request
app.post('/user', (req, res) => {
  const { creditCard, email } = req.body;

  // Encrypt the name
  const encryptedCard = encrypt(creditCard);

  // Save encrypted  to the database

  const user = new User({
    creditCard: encryptedCard ,
    email: email
  });// example of sequelize queries

  user.save().then(() => {
    res.send('User saved successfully');
  }).catch(err => {
    res.status(500).send('Error saving user');
  });
});


When you need to display the real data (in this case the card number) , you can use decrypt function inside your GET request.

// Decrypt function
function decrypt(encryptedData) {
  const [ivHex, encryptedText] = encryptedData.split(':');
  const ivBuffer = Buffer.from(ivHex, 'hex');
  const decipher = crypto.createDecipheriv('aes-256-cbc', encryptionKey, ivBuffer);
  let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
  decrypted  = decipher.final('utf8');
  return decrypted;
}

//GET request
app.get('/user/:id', (req, res) => {
  const userId = req.params.id;
 User.findById(userId).then(user => {
    if (!user) return res.status(404).send('User not found');

    // Decrypt the user's card 
    const decryptedCard = decrypt(user.creditCard);

 res.json({
      creditCard: decryptedCard ,
      email: user.email
    });
  }).catch(err => {
    res.status(500).send('Error retrieving user');
  });
});
  • Security Headers:

-Apply HTTP security headers like Strict-Transport-Security, X-Frame-Options, X-XSS-Protection, and X-Content-Type-Options to protect against various common vulnerabilities.

The simplest way to implement it is by using the helmet middleware in express after installing it npm install helmet.

const express = require('express');
const helmet = require('helmet');
const app = express();

app.use(helmet());

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

The alternative implementation is to manually setting Headers without Helmet as shown as below.

app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' https://trusted.cdn.com");
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  res.setHeader('Referrer-Policy', 'no-referrer');
  res.setHeader('Permissions-Policy', 'geolocation=(self), microphone=()');
  next();
});
  • CSRF Protection:

-Implement Cross-Site Request Forgery (CSRF) protection mechanisms such as CSRF tokens to prevent unauthorized actions in your app.

How CSRF Attacks Work:

-The user logs into your app, and the server sets a session cookie or an authentication token.
-The user then visits a malicious site while still logged in to your app in the background.
-The malicious site sends a request to your app using the logged-in user's credentials (session cookie) to perform unintended actions like transferring money or changing account details.

To prevent CSRF attacks, you need to ensure that requests made from your frontend are authenticated and originate from a trusted source. The most common way to protect against CSRF is by using CSRF tokens.

Implementation is easy by using cookie parser and express.urlencoded middlewares
first install them npm install csurf cookie-parser

const express = require('express');
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const app = express();

// Use cookie-parser for CSRF token storage (if using cookie-based tokens)
app.use(cookieParser());

// Initialize the CSRF middleware
const csrfProtection = csrf({ cookie: true });

// Use body-parser to parse form data
app.use(express.urlencoded({ extended: false }));

// routes here after applying the middlewares

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
  • Logging and monitoring:

-Ensure proper logging of security-related events such as failed login attempts and potential XSS attacks. Also, set up monitoring and alerts for abnormal behavior.

Conclusion:

This article aimed to highlight and summarize the key security principles that should be considered when developing full-stack JavaScript applications. We also explored practical ways to implement these principles efficiently. Additionally, these insights can be valuable when preparing for job interviews, especially when discussing various security measures and best practices in app development.

版本声明 本文转载于:https://dev.to/oussamabouyahia/key-security-principales-for-frontend-and-backend-development-o14?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何为PostgreSQL中的每个唯一标识符有效地检索最后一行?
    如何为PostgreSQL中的每个唯一标识符有效地检索最后一行?
    postgresql:为每个唯一标识符提取最后一行,在Postgresql中,您可能需要遇到与在数据库中的每个不同标识相关的信息中提取信息的情况。考虑以下数据:[ 1 2014-02-01 kjkj 在数据集中的每个唯一ID中检索最后一行的信息,您可以在操作员上使用Postgres的有效效率: ...
    编程 发布于2025-07-16
  • `console.log`显示修改后对象值异常的原因
    `console.log`显示修改后对象值异常的原因
    foo = [{id:1},{id:2},{id:3},{id:4},{id:id:5},],]; console.log('foo1',foo,foo.length); foo.splice(2,1); console.log('foo2', foo, foo....
    编程 发布于2025-07-16
  • Go语言垃圾回收如何处理切片内存?
    Go语言垃圾回收如何处理切片内存?
    Garbage Collection in Go Slices: A Detailed AnalysisIn Go, a slice is a dynamic array that references an underlying array.使用切片时,了解垃圾收集行为至关重要,以避免潜在的内存泄...
    编程 发布于2025-07-16
  • 将图片浮动到底部右侧并环绕文字的技巧
    将图片浮动到底部右侧并环绕文字的技巧
    在Web设计中围绕在Web设计中,有时可以将图像浮动到页面右下角,从而使文本围绕它缠绕。这可以在有效地展示图像的同时创建一个吸引人的视觉效果。 css位置在右下角,使用css float and clear properties: img { 浮点:对; ...
    编程 发布于2025-07-16
  • C++20 Consteval函数中模板参数能否依赖于函数参数?
    C++20 Consteval函数中模板参数能否依赖于函数参数?
    [ consteval函数和模板参数依赖于函数参数在C 17中,模板参数不能依赖一个函数参数,因为编译器仍然需要对非contexexpr futcoriations contim at contexpr function进行评估。 compile time。 C 20引入恒定函数,必须在编译时进行...
    编程 发布于2025-07-16
  • Python环境变量的访问与管理方法
    Python环境变量的访问与管理方法
    Accessing Environment Variables in PythonTo access environment variables in Python, utilize the os.environ object, which represents a mapping of envir...
    编程 发布于2025-07-16
  • Async Void vs. Async Task在ASP.NET中:为什么Async Void方法有时会抛出异常?
    Async Void vs. Async Task在ASP.NET中:为什么Async Void方法有时会抛出异常?
    在ASP.NET async void void async void void void void void的设计无需返回asynchroncon而无需返回任务对象。他们在执行过程中增加未偿还操作的计数,并在完成后减少。在某些情况下,这种行为可能是有益的,例如未期望或明确预期操作结果的火灾和...
    编程 发布于2025-07-16
  • 同实例无需转储复制MySQL数据库方法
    同实例无需转储复制MySQL数据库方法
    在同一实例上复制一个MySQL数据库而无需转储在同一mySQL实例上复制数据库,而无需创建InterMediate sqql script。以下方法为传统的转储和IMPORT过程提供了更简单的替代方法。 直接管道数据 MySQL手动概述了一种允许将mysqldump直接输出到MySQL clie...
    编程 发布于2025-07-16
  • 为什么使用Firefox后退按钮时JavaScript执行停止?
    为什么使用Firefox后退按钮时JavaScript执行停止?
    导航历史记录问题:JavaScript使用Firefox Back Back 此行为是由浏览器缓存JavaScript资源引起的。要解决此问题并确保在后续页面访问中执行脚本,Firefox用户应设置一个空功能。 警报'); }; alert('inline Alert')...
    编程 发布于2025-07-16
  • 可以在纯CS中将多个粘性元素彼此堆叠在一起吗?
    可以在纯CS中将多个粘性元素彼此堆叠在一起吗?
    [2这里: https://webthemez.com/demo/sticky-multi-header-scroll/index.html </main> <section> { display:grid; grid-template-...
    编程 发布于2025-07-16
  • PHP与C++函数重载处理的区别
    PHP与C++函数重载处理的区别
    作为经验丰富的C开发人员脱离谜题,您可能会遇到功能超载的概念。这个概念虽然在C中普遍,但在PHP中构成了独特的挑战。让我们深入研究PHP功能过载的复杂性,并探索其提供的可能性。在PHP中理解php的方法在PHP中,函数超载的概念(如C等语言)不存在。函数签名仅由其名称定义,而与他们的参数列表无关。...
    编程 发布于2025-07-16
  • 为什么HTML无法打印页码及解决方案
    为什么HTML无法打印页码及解决方案
    无法在html页面上打印页码? @page规则在@Media内部和外部都无济于事。 HTML:Customization:@page { margin: 10%; @top-center { font-family: sans-serif; font-weight: bo...
    编程 发布于2025-07-16
  • Java的Map.Entry和SimpleEntry如何简化键值对管理?
    Java的Map.Entry和SimpleEntry如何简化键值对管理?
    A Comprehensive Collection for Value Pairs: Introducing Java's Map.Entry and SimpleEntryIn Java, when defining a collection where each element com...
    编程 发布于2025-07-16
  • 如何克服PHP的功能重新定义限制?
    如何克服PHP的功能重新定义限制?
    克服PHP的函数重新定义限制在PHP中,多次定义一个相同名称的函数是一个no-no。尝试这样做,如提供的代码段所示,将导致可怕的“不能重新列出”错误。 但是,PHP工具腰带中有一个隐藏的宝石:runkit扩展。它使您能够灵活地重新定义函数。 runkit_function_renction_re...
    编程 发布于2025-07-16
  • PHP未来:适应与创新
    PHP未来:适应与创新
    PHP的未来将通过适应新技术趋势和引入创新特性来实现:1)适应云计算、容器化和微服务架构,支持Docker和Kubernetes;2)引入JIT编译器和枚举类型,提升性能和数据处理效率;3)持续优化性能和推广最佳实践。 引言在编程世界中,PHP一直是网页开发的中流砥柱。作为一个从1994年就开始发展...
    编程 发布于2025-07-16

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3