Skip to main content
The following steps provide a structured guide for integrating authentication with Fiskil’s Data Provider platform.

Authentication Flow

The diagram below illustrates how authentication works between the Fiskil Data Provider and your Resource Server:

JWT

The Fiskil Data Provider uses JSON Web Tokens (JWTs) to authenticate to your Resource Server. JWTs are well-suited to Open Data platforms since they support fine-grained access control and short lifetimes. Requests to your API include a JWT in the Authorization header using the Bearer scheme. Each JWT is signed by the Data Provider. The public keys are exposed via a JWKS endpoint (shared during onboarding). Tokens include a kid JOSE header so your server can select the correct JWK. The JWKS URL for your instance is available in the Console under Settings → Domains.

Validating the JWT

Once you have the public key you must validate the JWT. When you validate the JWT, ensure that:
  • The signature is valid.
  • The token is not expired (check the exp claim).
  • The sub and iss claims match your Data Provider subdomain (provided during onboarding).
  • The aud is the URI of the resource being requested on the Data API.
  • The jti has not been used before.

Node.js Implementation Example

Here’s a complete Node.js example showing how to validate JWTs from Fiskil’s Data Provider:
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

// Configure JWKS client (replace with your actual JWKS URL)
const client = jwksClient({
  jwksUri: 'https://cdr.your-domain.com/.well-known/jwks.json',
  cache: true,
  cacheMaxAge: 3600000, // 1 hour
  rateLimit: true,
  jwksRequestsPerMinute: 5
});

// Store used JTIs to prevent replay attacks
const usedJtis = new Set();

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) {
      return callback(err);
    }
    const signingKey = key.getPublicKey();
    callback(null, signingKey);
  });
}

async function validateFiskilJWT(token, requestUrl, expectedIssuer) {
  try {
    // Verify and decode the JWT
    const decoded = jwt.verify(token, getKey, {
      algorithms: ['PS256', 'RS256'],
      issuer: expectedIssuer,
      audience: requestUrl,
      clockTolerance: 30 // Allow 30 second clock skew
    });

    // Additional validation checks
    const now = Math.floor(Date.now() / 1000);

    // Check token hasn't expired
    if (decoded.exp <= now) {
      throw new Error('Token has expired');
    }

    // Check subject matches issuer
    if (decoded.sub !== expectedIssuer) {
      throw new Error('Subject claim does not match expected issuer');
    }

    // Check for JTI replay attack
    if (usedJtis.has(decoded.jti)) {
      throw new Error('Token has already been used (replay attack)');
    }

    // Store JTI to prevent replay
    usedJtis.add(decoded.jti);

    // Clean up old JTIs periodically (implement based on your needs)
    // You might want to use Redis or another storage for production

    return {
      valid: true,
      claims: decoded
    };

  } catch (error) {
    return {
      valid: false,
      error: error.message
    };
  }
}

// Express.js middleware example
function authenticateJWT(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing or invalid Authorization header' });
  }

  const token = authHeader.substring(7); // Remove 'Bearer ' prefix
  const requestUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
  const expectedIssuer = 'https://cdr.your-domain.com'; // Replace with your domain

  validateFiskilJWT(token, requestUrl, expectedIssuer)
    .then(result => {
      if (result.valid) {
        req.jwt = result.claims;
        next();
      } else {
        console.error('JWT validation failed:', result.error);
        res.status(401).json({ error: 'Invalid token' });
      }
    })
    .catch(error => {
      console.error('JWT validation error:', error);
      res.status(401).json({ error: 'Authentication failed' });
    });
}

// Usage in your Express route
app.get('/v1/energy/customer/:customerId/accounts', authenticateJWT, (req, res) => {
  // Access validated JWT claims via req.jwt
  console.log('Authenticated request from:', req.jwt.iss);
  console.log('Request ID:', req.jwt.jti);

  // Your API logic here
  res.json({ accounts: [] });
});
This example stores used JTIs in memory. For production systems, use a distributed cache like Redis to prevent replay attacks across multiple server instances.
Consider implementing JTI cleanup logic to remove expired entries and prevent memory leaks. You can use the exp claim to determine when to clean up each JTI.

Example: authenticated request

The example below shows a JWT used to authenticate a request to a hypothetical utilities provider, Acme Company.
GET /v1/energy/customer/bob/accounts HTTP/2
Host: data-api.acme-company.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImVjZDdjMjEyLWM0YmQtNGRhNi04ZjhkLTgyNjUxODdiNDIwMCJ9.eyJpc3MiOiJodHRwczovL2Nkci5hY21lLWVuZXJneS5jb20iLCJzdWIiOiJodHRwczovL2Nkci5hY21lLWVuZXJneS5jb20iLCJhdWQiOiJodHRwczovL2RhdGEtYXBpLmFjbWU... (truncated)
For security and readability, avoid logging full tokens. Log only minimal metadata (for example, the jti).

Decoded JWT

{
  "alg": "PS256",
  "typ": "JWT",
  "kid": "ecd7c212-c4bd-4da6-8f8d-8265187b4200"
}

Claims

{
  "iss": "https://cdr.acme-company.com",
  "sub": "https://cdr.acme-company.com",
  "aud": "https://data-api.acme-company.com/v1/energy/customer/bob/accounts",
  "iat": 1691939022,
  "exp": 1692939022,
  "jti": "2e7da1da-e410-48ec-827f-e17658dd87b2"
}
JWT is a battle-tested specification for sharing cryptographically secured claims. Use a production-grade library to fetch JWKS and verify JWTs in your chosen programming language. For more information on JWT see the references below.

Firewall

While JWT is sufficient to authenticate the Data Provider with your API, we recommend adding a firewall IP allow-list to further protect your data.
  • Fiskil provides IP addresses for allow-listing during onboarding.
  • The IP addresses are also available in the Console under Settings → Resource Server.

Final Notes

Security is a complex topic and often a trade-off is being made between strictness and ease-of-use. If the above security mechanisms do not meet your expectations then please reach out to the Fiskil team and we can discuss alternative security schemes to ensure a smooth onboarding process while protecting your customers’ sensitive information.