10 Api security best practices you must know

Ashish Dhakal
readytowork, Inc.
Published in
5 min readApr 24, 2024

With the rise of microservices architecture, APIs have become the backbone of modern web applications. However, along with the convenience they offer, APIs also introduce security challenges that need to be addressed proactively. In this article, we’ll explore some of the must-have techniques with an example in go and gin that will enhance API security

Understanding API Security

API security safeguards the integrity, confidentiality, and availability of data exchanged between clients and servers through APIs. Implementing robust security measures is crucial to mitigate potential risks, whether authenticating users, protecting sensitive data, or preventing unauthorized access.

1. Input Validation and sanitization

Input validation is crucial for preventing various types of attacks such as SQL injection, Cross-Site Scripting (XSS), and Command Injection. With Gin, we can use libraries like binding and validator to validate incoming request data.

Example:


type User struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}

func main() {
r := gin.Default()

r.POST("/login", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Validate user input further and process login
if validationErr := validator.Validate.Struct(&user); validationErr != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
})
}

2. Authentication

Implementing strong authentication mechanisms is essential for controlling access to the API endpoints. Use techniques like JWT (JSON Web Tokens) or Firebase for authentication..

func createToken(user User) (string, error) {
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["username"] = user.Username
// Add other claims like expiration time, etc.
tokenString, err := token.SignedString([]byte("secret"))
if err != nil {
return "", err
}
return tokenString, nil
}

3. Authorization/RBAC (Role-Based Access Control):

Enforce role-based access control to restrict access to certain endpoints based on user roles. We can create and use custom permission middleware for API endpoints. RBAC should be implemented if a system has multiple types of users [admin, staff, viewer].

func StaffUserAccessMiddleware(c *gin.Context) {
userId := c.GetHeader("user_id")
// Check if user has staff role
if !IsStaff(userId) {
// Unauthorized access, return 401 status
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
c.Next()
}

func IsStaff(userId) bool {
// checkif the user has specified role or not
staffUser := db.Table("user_roles").Where("user_id = ? AND Role = 'staff'", user_Id)
if staffUser {
return true
}
return false
}

func main() {
r := gin.Default()

// Protected route with authorization middleware
r.GET("/staff-only", StaffUserAccessMiddleware, func(c *gin.Context) {
c.String(http.StatusOK, "autorized")
})

}

4. Secure Password Storage

When storing user passwords, never store them in plaintext. Use strong cryptographic hash functions like bcrypt to securely hash passwords before storing them in the database.

Example:

// hash pasword 
func hashText(text string) ([]byte, error) {
return bcrypt.GenerateFromPassword([]byte(text), bcrypt.DefaultCost)
}


func saveUser(c *gin.Context) {
password := "abcabc"
// get hash for plain password
hashPassword := hashText(password)
db.Save("password = ?", hashPassword)

}

5. SQL Injection Prevention

SQL injection is a type of cyber attack where malicious SQL code is inserted into input fields of a web application, tricking the application into executing unintended SQL commands. This can lead to unauthorized access, data leakage, and even data manipulation.

Avoid building SQL queries dynamically using string concatenation. Instead, use parameterized queries or prepared statements to prevent SQL injection attacks.

Example:


// function to fetch user by ID (vulnerable to SQL injection)
func getUser(id string) (user, error) {
// Construct SQL query (Vulnerable to SQL injection)
query := fmt.Sprintf("SELECT id, username FROM users WHERE id = %s", userID)
rows, err := db.Raw(query).Err
return rows, err
}

// function to fetch user by ID (secure against SQL injection)
func getUser(id string) (user, error) {
// Prepare SQL statement with placeholders
query := "SELECT id, username FROM users WHERE id = ?"
rows, err := db.Query(query, id)
return rows, err
}

6. Logging

If logging is not properly configured, it may inadvertently log sensitive information such as user credentials, API keys, or other confidential data. This information could be exposed if logs are not properly secured or if they are accessed by unauthorized parties.

Example:

7. CSRF tokens

Implement CSRF protection by generating unique tokens for each session and validating them with incoming requests. CSRF attacks are particularly dangerous because they exploit the inherent trust that web applications have in the authentication credentials of users

func main() {
r := gin.Default()
// Middleware to generate and verify CSRF tokens
csrfMiddleware := csrf.Middleware(csrf.Options{
Secret: "xxx",
TokenLength: 32,
})

// Protected route using CSRF middleware
r.POST("/protected", csrfMiddleware, func(c *gin.Context) {
c.String(200, "CSRF token verified!")
})

}

8. Implement a defense mechanism against XSS

XSS, or Cross-Site Scripting, is a security vulnerability in web applications. It occurs when an attacker injects malicious scripts (usually written in JavaScript) into web pages viewed by other users. These scripts are then executed within the context of the victim’s browser, allowing the attacker to steal sensitive information, manipulate the appearance of the page, or perform other malicious actions.

XSS vulnerabilities can have serious consequences, including theft of sensitive information (such as cookies or session tokens), unauthorized access to user accounts, defacement of websites, and even full compromise of the entire web application. We should implement proper input validation, output encoding, and other security measures in web applications to prevent XSS attacks.

Example:

I. Input Validation:

Use input validation libraries validator to ensure that user input meets expected criteria. I’ve demonstrated earlier about input validation above.

II. Output Encoding:

func main() {
router := gin.Default()

router.GET("/data", func(c *gin.Context) {
userInput := "<script>alert('XSS attack');</script>"
encodedInput := html.EscapeString(userInput)
c.JSON(http.StatusOK, gin.H{"data": encodedInput})
})

router.Run(":8080")
}

III. Content Security Policy (CSP):

func main() {
router := gin.Default()

router.Use(func(c *gin.Context) {
c.Header("Content-Security-Policy", "default-src 'self'")
c.Next()
})

// Define routes and handlers here

router.Run(":8080")
}

9. Rate Limiting and Throttling

Lack of rate limiting can expose APIs to security vulnerabilities such as brute-force attacks, where attackers attempt to guess user credentials or exploit vulnerabilities by making a large number of requests in a short period. Rate limiting can help mitigate these risks by limiting the rate at which requests can be made.

10. Graceful Error Handling

Define a set of standardized error response formats for APIs. This includes consistent HTTP status codes (e.g., 4xx for client errors, 5xx for server errors) and clear, concise error messages. To prevent information leakage, avoid revealing sensitive information in error messages, such as stack traces or detailed system internals.

Conclusion

Developing secure APIs is not a one-time task but an ongoing process that requires vigilance and adherence to best practices. By following the security guidelines outlined in this article, we can significantly reduce the risk of security breaches and ensure the confidentiality, integrity, and availability of APIs.

References:

https://datadome.co/guides/api-protection/api-security-risks-how-to-mitigate/

https://cloud.google.com/sql/docs/mysql/best-practices

--

--