Did you know that if APIs are left unprotected, anyone can use them, potentially resulting in numerous calls that can bring down the API (DoS/DDoS Attack) or even update the data without the user’s consent?
Let’s look at this from the perspective of a curious developer. Sometimes, I only want to trace the network request in the browser by launching the Dev Tools (commonly by pressing the F12 key) and looking at the Network Tab.
In the past, WebApps were directly linked with Sessions. Based on whether a session is valid, the request would go through. If no request is performed in a given time frame, it would simply exhaust the session in 20 minutes (default in some servers unless configured). Now, we build WebApps purely on the client side, allowing them to consume Rest-based APIs. Our services are strictly API-first because it will enable us to scale them quickly and efficiently. Modern WebApps are built using frameworks like ReactJS, NextJS, Angular, and VueJS, resulting in single-page applications (SPA) that are purely client-side.
Let’s look at this technically: HTTP is a Stateless Protocol. This means the server doesn’t need to remember anything between requests when using HTTP. It just receives a URL and some headers and sends back data.
We use attributes like [Authorize] in our Asp.Net Core-based Web APIs to authorize them securely. This involves generating JWT (JSON Web Tokens) and sending them along with the Login Response to be stored on the Client Side. Any future requests include the JWT Token, which gets automatically validated. JWTs are an open, industry-standard RFC 7519 method for representing claims securely between two parties and can be used with various technologies beyond Asp.Net Core, such as Spring.
When you send the JWT back to the server, it’s typically sent using the Header in subsequent requests. The server generally looks for the Authorization Header values, which comprise the keyword Bearer, followed by the JWT Token.
<code>Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFudWJoYXYgUmFuamFuIiwiaWF0IjoxNTE2MjM5MDIyfQ.ALU4G8LdHbt6FCqxtr2hgfJw1RR7nMken2x0SC_hZ3g</code>
The above is an example of the token being sent. You can copy the JWT Token and visit https://jwt.io to check its values.
If I missed something about JWT, feel free to comment.
This is one of the reasons why I thought of protecting my APIs. I stumbled upon features like Remember Me when developing a WebApp using Asp.Net Core WebAPI as the backend and ReactJS as the front end. Although a simple feature, Remember Me could be implemented in various ways. Initially, I thought, why complicate things with cookies? I will be building mobile apps for the site anyway! However, it always bugged me that my JWT was stored in LocalStorage. The reason is simple: I can have users for this website ranging from someone who has zero knowledge of how it works to someone like me or, at worst, a potential hacker. A simple attack vector is impersonation if my JWT token is accessed. Any JavaScript can easily access this token stored in Local Storage. Due to this, I thought, yes, we can save the JWT in a Cookie sent from the Server, but it needs properties like HttpOnly, etc. But what if my API is used from a Mobile App? Considering the Cookie, it’s not a norm to extract the said token from a Cookie (although it is doable). Thus, I started looking into TOTP.
Now, let’s explore TOTP (Time-based One-Time Password) and its role in securing API requests. TOTP authenticates users based on a shared secret key and the current time. It generates a unique, short-lived password that changes every 30 seconds.
Have you heard of TOTP before? It’s the same thing when you use your Microsoft Authenticator or Google Authenticator Apps to provide a 6-digit code for Login using 2FA (2-Factor Authentication).
Why Use TOTP for API Security?
While JWTs provide a robust mechanism for user authentication and session management, they are not immune to attacks, especially if the tokens are stored insecurely or intercepted during transmission. TOTP adds an extra layer of security by requiring a time-based token in addition to the JWT. This ensures that even if a JWT is compromised, the attacker still needs the TOTP to authenticate, significantly reducing the risk of unauthorized access.
Implementing TOTP in APIs
Here’s a high-level overview of how to implement TOTP for API requests:
1. Generate a Shared Secret: When a user registers or logs in, generate a TOTP secret key dynamically, hidden from any storage. This key is used to create TOTP tokens.
2. TOTP Token Generation: Use libraries to generate TOTP tokens based on the shared secret and the current time.
3. API Request Validation: On the server side, validate the incoming JWT as usual. Additionally, the TOTP token is required in the request header or body. Validate the TOTP token using the same shared secret and the current time.
<code>// Example code snippet for validating TOTP in Node.js const speakeasy = require('speakeasy'); // Secret stored on the server const secret = 'KZXW6YPBOI======'; function validateTOTP(token) { const verified = speakeasy.totp.verify({ secret: secret, encoding: 'base32', token: token, }); return verified; } // On receiving an API request const tokenFromClient = '123456'; // TOTP token from client if (validateTOTP(tokenFromClient)) { console.log('TOTP token is valid!'); } else { console.log('Invalid TOTP token!'); } </code>
Using TOTP ensures that even if the JWT is compromised, unauthorized access is prevented because the TOTP token, which changes every 30 seconds, is required.