Why your environment variables aren’t safe

Mayowa Julius Ogungbola - Feb 27 - - Dev Community

When building fintech applications that handle sensitive payment data, you quickly learn that security isn’t something you can take lightly. Environment variables have long been considered a secure way to store sensitive credentials like API keys, OAuth tokens, and database passwords. But are they really safe?

For fintech payment apps, security is non-negotiable. While .env files are a common approach to storing sensitive data, they come with risks that make them unsuitable for building applications on production. Some of these risks involve accidental exposure through misconfigurations, lack of encryption, or being unintentionally committed to repositories.

Imagine you’re in charge of a high-security bank vault. This vault holds millions of dollars, and only authorized personnel can access it. Instead of keeping the vault’s key in a restricted, access-controlled safe, you leave it in an unlocked desk drawer in a staff-only area.
At first, this might seem fine. Only employees work there, right? But all it takes is one:

  • Accidental exposure (someone sees it and remembers the combination).
  • Insider threat (a disgruntled employee takes advantage).
  • Security breach (an outsider gains access to the office and finds the key).

This is how environment variables work in applications. While they seem hidden, they are often accessible to logs, memory dumps, CI/CD pipelines, and even to attackers who gain limited system access.

This article discusses how environment variables can be a security risk and shows you how to keep your system truly secure by setting up a secrets manager and implementing key rotation for your Flutterwave secret keys. We’ll also touch on using OAuth as a more secure method for authorization.

The Hidden Risks of Environment Variables

At first glance, environment variables offer a simple solution: Keep your sensitive data out of your source code. You might even feel relieved knowing your API and secret keys are tucked away in a .env file that you don’t push to Git. However, the convenience comes at a cost. Here’s why:

Accidental Exposure
It’s common for developers to expose their environment variables, maybe through a misconfigured Git repository, a CI/CD pipeline logging excess information, or an error message that prints out variables during debugging; secrets can leak out in various ways.

  • Git Mishaps: Even with a properly configured .gitignore, mistakes happen. A developer might accidentally commit a backup of the .env file or even a misconfigured version that contains real keys.
  • CI/CD Pipelines: Build tools and deployment scripts often load environment variables into memory. If these logs aren’t handled properly, your secrets can end up on a public dashboard or in an accessible log file.
  • Debugging and Error Reporting: Stack traces might include environment data when an error occurs. If your error logging isn’t secure, this sensitive data could be exposed to unintended parties.

Lack of Key Rotation
Environment variables, like those used to store API keys in a .env file, are typically static. Once set, they remain unchanged until someone manually rotates it. In contrast, key rotation is a process that involves periodically changing your cryptographic keys to enhance security and reduce the risk of compromise.
Without a similar rotation process, static keys in environment variables pose a significant risk: if a key is ever compromised, it might remain valid for an extended period, giving attackers ample time to exploit your system.

Risk of Internal Exposure
With environment variables, anyone who has access to the deployment environment can see the secrets. This means that even if your production environment is secure, the fact that these secrets are available in plain text means that an insider threat or even an accidental leak from a trusted developer can lead to disaster.

Moving Beyond Environment Variables

Given these risks, how can you secure your sensitive data in fintech applications, especially when dealing with payment APIs?

Let’s walk through some steps to securing your secret variables:

1. Configure a secrets manager

  • Consider using a dedicated secrets management tool instead of storing your secrets in a .env file. Tools like AWS Secrets Manager, Google Cloud Secrets Manager, and many others are designed to securely store, rotate, and manage access to your secrets.

secrets_manager

How to set up a secrets manager

Let’s walk through an example using AWS Secrets Manager:

  • Store your secret:
    • Go to your AWS secrets manager console.
    • Create a new secret and choose the option “Other type of secrets” if your key fits into predefined categories (or any other sensitive data) and give it a description name, for example, flutterewave-prod-api-key.

2. Access the secret in your application

  • Instead of loading the key from a .env file, your application will now fetch the key at runtime.

Here’s a Node.js example using the AWS SDK:

    const AWS = require('aws-sdk');
    const secretsManager = new AWS.SecretsManager({ region: 'us-east-1' });

    const secretName = "<REPLACE_WITH_YOUR_SECRET NAME>";
    async function getSecretValue(secretName) {
      try {
        const data = await secretsManager.getSecretValue({ SecretId: secretName }).promise();
        if ('SecretString' in data) {
          return data.SecretString;
        }
        throw new Error('Secret is binary. This example only handles text secrets.');
      } catch (err) {
        console.error('Error retrieving secret:', err);
        throw err;
      }
    }
    (async () => {
      const flutterwaveApiKey = await getSecretValue('flutterwave-prod-api-key');
      console.log('Flutterwave API Key retrieved securely!');
    })();
Enter fullscreen mode Exit fullscreen mode
  • This approach prevents your API key from being stored in plaintext on your server or repository.

3. Implement key rotation

  • One of the benefits of using a secrets manager is the ability to rotate keys automatically.
  • Configure your secrets manager to rotate keys regularly (e.g., every 30 or 90 days). This minimizes the window of opportunity for an attacker in case a key is compromised.

Encryption and Data protection

Even when you move your secret into a dedicated secrets manager, encryption remains vital for security. Rather than leaving sensitive data in plain text (even briefly), encryption keeps your data protected at rest and in transit.

Here’s why encryption matters:

  • Data at rest: When your secrets are stored in a manager (such as AWS Secrets Manager or HashiCorp Vault), they’re encrypted using a strong algorithm that keeps the secret unreadable without the current decryption keys.
  • Data in transit: Whenever your application retrieves a secret, the request should occur over an encryption channel (HTTPS/TLS); this protects your API keys and credentials from being intercepted during transmission.
  • Operational security: Encrypting sensitive data minimizes the risk of accidental exposure (like logging an environment variable by mistake), and a strong encryption system with strict access controls lets insiders see only what they can access.

When working with payment methods like Flutterwave’s direct card charges for card payments, an encryption key is provided to encrypt your payload, which is essential for a FinTech solution that requires transmitting users' card details when making the request.

Now that you understand the importance of encryption when building complex financial applications, let's explore how to apply it effectively by following these best practices.

Best Practices for Using Encryption

  • Use managed solutions: Cloud-based secrets managers automatically handle encryption and decryption. They’re designed to store security securely and platform regular key rotations.
  • Always use HTTPS: Make sure every call that fetches secrets or exchanges sensitive data uses HTTPS, safeguarding against man-in-the-middle attacks.
  • Regular auditing: Use tools to audit your encryption keys and verify that your data is being encrypted at rest and in transit.

Embrace OAuth for Secure API Authentication

For fintech payment applications, relying solely on static API keys (even with encryption) isn’t ideal, as OAuth 2.0 is widely regarded as a more robust alternative by replacing long-lived, static credentials with short-lived access tokens that can be scoped, monitored, and revoked as needed.

oauth_flow

Why OAuth is a better alternative:

  • Short-lived tokens: Temporary tokens with a limited lifespan to reduce the risk of unauthorized access if compromised
  • Scoped permissions: Access control that restricts a token’s actions to specific resources or operations, enhancing security.
  • Dynamic management: The ability to adjust or rotate secrets and access permissions in real time without application downtime.

How to Implement OAuth In Flutterwave

While Flutterwave v3 APIs don’t support OAuth, v4 (coming soon) introduces OAuth support features. In v3, you can integrate with third-party providers like Auth0 to secure your API calls.

oauth_transaction

For your Flutterwave API calls you might set up an OAuth 2.0 flow that looks something like this:

Step 1: Redirect to the authorization endpoint
When users try to log into your platform, you first need to redirect them to your OAuth provider’s authorization URL. Here’s an example using Express:

    const express = require('express');
    const app = express();
    const AUTHORIZATION_URL = 'https://your-auth-provider.com/authorize';
    const CLIENT_ID = 'YOUR_CLIENT_ID';
    const REDIRECT_URI = 'http://localhost:3000/callback';
    const SCOPE = 'openid profile email';
    const RESPONSE_TYPE = 'code';
    app.get('/login', (req, res) => {
      const authUrl = `${AUTHORIZATION_URL}?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=${SCOPE}&response_type=${RESPONSE_TYPE}`;
      res.redirect(authUrl);
    });
Enter fullscreen mode Exit fullscreen mode

In this snippet, when users navigate to log in, they are redirected to the provider’s login page. There, they are authenticated, and the provider then redirects them back to your application with an authorization code.

Step 2: Handling the callback and exchanging the code for a token
Once the user authenticates, the provider sends an authorization code to your callback URL. Your application then needs to exchange this code for an access token:

    const axios = require('axios');
    app.get('/callback', async (req, res) => {
      const authorizationCode = req.query.code;
      try {
        const tokenResponse = await axios.post('https://your-auth-provider.com/oauth/token', {
          grant_type: 'authorization_code',
          code: authorizationCode,
          redirect_uri: REDIRECT_URI,
          client_id: CLIENT_ID,
          client_secret: 'YOUR_CLIENT_SECRET'
        }, {
          headers: {
            'Content-Type': 'application/json'
          }
        });
        const accessToken = tokenResponse.data.access_token;
        const expiresIn = tokenResponse.data.expires_in;
        res.send(`Access Token: ${accessToken} (Expires in ${expiresIn} seconds)`);
      } catch (error) {
        console.error('Error exchanging code for token:', error);
        res.status(500).send('Authentication failed.');
      }
    });
    app.listen(3000, () => console.log('Server running on http://localhost:3000'));
Enter fullscreen mode Exit fullscreen mode

Here’s what happens:

  • Receive the code: Your /callback endpoint receives the authorization code as a query parameter.
  • Token exchange: You then send a POST request to the provider’s token endpoint. This request includes your client credentials, the received authorization code, and your redirect URI.
  • Retrieve the token: If successful, the provider returns an access token (and optionally a refresh token), which you can then use to authenticate subsequent API calls.

Step 3: Using the Access Token to secure API calls
Once you have the access token, you can secure your API calls. For instance, if you’re making a request to Flutterwave’s API, your call might look like this:

    app.get('/payments', async (req, res) => {
      const accessToken = 'YOUR_RETRIEVED_ACCESS_TOKEN'; 
      try {
        const apiResponse = await axios.get('https://api.flutterwave.com/payments', {
          headers: {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json'
          }
        });
        res.json(apiResponse.data);
      } catch (error) {
        console.error('Error making secure API call:', error);
        res.status(500).send('API request failed.');
      }
    });
Enter fullscreen mode Exit fullscreen mode

The example above demonstrates:

  • Token usage: The access token is included in the Authorization header using the Bearer schema.
  • Secure communication: As with all secure API calls, the request is made over HTTPS, ensuring your sensitive data isn’t exposed in transit.

Best Practices for Secure Fintech Development

Beyond just switching out environment variables for a secrets manager and OAuth, there are several additional practices you should adopt to ensure your fintech applications remain secure:

Enforce the least privilege

Always adhere to the principle of least privilege. Only grant your applications and users the minimal permissions they need to perform their tasks and minimize the potential damage if an access token or secret is compromised.

Monitor and audit

Monitor access to your secrets and audit logs for any unusual activity. Use tools that alert you to anomalies, such as multiple failed access attempts or unexpected access from new IP addresses.

Encrypt data in transit and at rest

Always use TLS/SSL for data in transit and ensure that any data stored (including secrets) is encrypted at rest. This adds an extra layer of defense against man-in-the-middle attacks and unauthorized data access.

Educate your team

Security is not just a technical challenge, it’s also a people challenge. Ensure your team understands the risks associated with storing sensitive data in environment variables and follows best practices for secure coding and deployment.

Wrapping up

You can significantly reduce the risk of sensitive data leaks by moving to a dedicated secrets manager, leveraging OAuth for dynamic token-based authorization, and following best practices like least privilege and continuous monitoring. Not only do these measures help protect your fintech payment apps, but they also ensure you remain compliant with industry standards and protect your customers’ trust.

Remember, every time you store a secret in an environment variable, you’re essentially leaving a sticky note on your desk. It’s time to upgrade to a secure vault, one that only you and your trusted systems can access.

If you’re ready to build secure fintech products following best practices and security standards to safeguard your applications, sign up for a Flutterwave account to get started and explore the developer documentation.

. . . . . . . . . .