Server Setup

Server Setup

Configure your backend server to validate JWT tokens from Grantiva and secure your API endpoints.

Overview

After your iOS app receives a JWT token from Grantiva, you need to validate this token on your backend to ensure requests are coming from authenticated devices. This guide covers common server implementations.

JWT Token Structure

Grantiva issues JWT tokens with the following structure:

Header

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

{
  "sub": "device_key_id",
  "iss": "grantiva",
  "aud": "com.yourcompany.app",
  "exp": 1704067200,
  "iat": 1704063600,
  "deviceIntelligence": {
    "riskScore": 15,
    "deviceIntegrity": "high",
    "jailbreakDetected": false
  },
  "permissions": ["basic", "payments", "transfers"],
  "tenantId": "your-tenant-id"
}

Server Implementation

Node.js / Express

Install required packages:

npm install jsonwebtoken express

Middleware implementation:

const jwt = require('jsonwebtoken');

// Your JWT secret from Grantiva dashboard
const JWT_SECRET = process.env.GRANTIVA_JWT_SECRET;

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  jwt.verify(token, JWT_SECRET, (err, decoded) => {
    if (err) {
      return res.status(403).json({ error: 'Invalid token' });
    }

    // Check risk score
    if (decoded.deviceIntelligence.riskScore > 50) {
      return res.status(403).json({ error: 'Device risk too high' });
    }

    // Check for jailbreak
    if (decoded.deviceIntelligence.jailbreakDetected) {
      return res.status(403).json({ error: 'Jailbroken device not allowed' });
    }

    req.user = decoded;
    next();
  });
}

// Protected route example
app.get('/api/secure-data', authenticateToken, (req, res) => {
  // Access device info from req.user
  const { deviceIntelligence, permissions } = req.user;
  
  // Check permissions
  if (!permissions.includes('payments')) {
    return res.status(403).json({ error: 'Payment permission required' });
  }
  
  res.json({ data: 'Secure data for authenticated device' });
});

Python / FastAPI

Install required packages:

pip install fastapi python-jose[cryptography] python-multipart

Implementation:

from fastapi import FastAPI, Depends, HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import JWTError, jwt
from typing import Optional
import os

app = FastAPI()
security = HTTPBearer()

JWT_SECRET = os.getenv("GRANTIVA_JWT_SECRET")
JWT_ALGORITHM = "HS256"

def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)):
    token = credentials.credentials
    
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
        
        # Check risk score
        risk_score = payload.get("deviceIntelligence", {}).get("riskScore", 100)
        if risk_score > 50:
            raise HTTPException(status_code=403, detail="Device risk too high")
        
        # Check jailbreak status
        if payload.get("deviceIntelligence", {}).get("jailbreakDetected", False):
            raise HTTPException(status_code=403, detail="Jailbroken device not allowed")
        
        return payload
    except JWTError:
        raise HTTPException(status_code=403, detail="Invalid authentication token")

@app.get("/api/secure-data")
async def get_secure_data(token_data: dict = Depends(verify_token)):
    # Check permissions
    permissions = token_data.get("permissions", [])
    if "payments" not in permissions:
        raise HTTPException(status_code=403, detail="Payment permission required")
    
    return {
        "data": "Secure data for authenticated device",
        "device_id": token_data.get("sub"),
        "risk_score": token_data.get("deviceIntelligence", {}).get("riskScore")
    }

Go / Gin

Install required packages:

go get github.com/gin-gonic/gin
go get github.com/golang-jwt/jwt/v5

Implementation:

package main

import (
    "net/http"
    "os"
    "strings"
    
    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
)

type DeviceIntelligence struct {
    RiskScore         int    `json:"riskScore"`
    DeviceIntegrity   string `json:"deviceIntegrity"`
    JailbreakDetected bool   `json:"jailbreakDetected"`
}

type Claims struct {
    DeviceIntelligence DeviceIntelligence `json:"deviceIntelligence"`
    Permissions        []string           `json:"permissions"`
    TenantID           string             `json:"tenantId"`
    jwt.RegisteredClaims
}

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "No token provided"})
            c.Abort()
            return
        }
        
        tokenString := strings.Replace(authHeader, "Bearer ", "", 1)
        
        token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
            return []byte(os.Getenv("GRANTIVA_JWT_SECRET")), nil
        })
        
        if err != nil || !token.Valid {
            c.JSON(http.StatusForbidden, gin.H{"error": "Invalid token"})
            c.Abort()
            return
        }
        
        claims := token.Claims.(*Claims)
        
        // Check risk score
        if claims.DeviceIntelligence.RiskScore > 50 {
            c.JSON(http.StatusForbidden, gin.H{"error": "Device risk too high"})
            c.Abort()
            return
        }
        
        // Check jailbreak
        if claims.DeviceIntelligence.JailbreakDetected {
            c.JSON(http.StatusForbidden, gin.H{"error": "Jailbroken device not allowed"})
            c.Abort()
            return
        }
        
        c.Set("claims", claims)
        c.Next()
    }
}

func main() {
    r := gin.Default()
    
    api := r.Group("/api")
    api.Use(AuthMiddleware())
    
    api.GET("/secure-data", func(c *gin.Context) {
        claims := c.MustGet("claims").(*Claims)
        
        // Check permissions
        hasPaymentPermission := false
        for _, perm := range claims.Permissions {
            if perm == "payments" {
                hasPaymentPermission = true
                break
            }
        }
        
        if !hasPaymentPermission {
            c.JSON(http.StatusForbidden, gin.H{"error": "Payment permission required"})
            return
        }
        
        c.JSON(http.StatusOK, gin.H{
            "data": "Secure data for authenticated device",
            "deviceId": claims.Subject,
            "riskScore": claims.DeviceIntelligence.RiskScore,
        })
    })
    
    r.Run(":8080")
}

Security Best Practices

  • Validate all claims: Check expiration, issuer, and audience
  • Implement risk-based access: Adjust allowed actions based on risk score
  • Log attestation events: Track device attestations for security monitoring
  • Rotate secrets regularly: Update JWT secrets periodically
  • Use HTTPS only: Never transmit tokens over unencrypted connections
  • Implement rate limiting: Prevent token abuse

Risk-Based Access Control

Implement different access levels based on risk scores:

function getRiskLevel(riskScore) {
  if (riskScore <= 20) return 'low';
  if (riskScore <= 50) return 'medium';
  if (riskScore <= 75) return 'high';
  return 'critical';
}

function enforceRiskPolicy(req, res, next) {
  const { deviceIntelligence } = req.user;
  const riskLevel = getRiskLevel(deviceIntelligence.riskScore);
  
  // Define allowed actions per risk level
  const policies = {
    low: ['read', 'write', 'payment', 'transfer'],
    medium: ['read', 'write', 'payment'],
    high: ['read', 'write'],
    critical: ['read']
  };
  
  req.allowedActions = policies[riskLevel];
  next();
}

// Usage in routes
app.post('/api/transfer', authenticateToken, enforceRiskPolicy, (req, res) => {
  if (!req.allowedActions.includes('transfer')) {
    return res.status(403).json({ 
      error: 'Transfer not allowed for current risk level',
      riskScore: req.user.deviceIntelligence.riskScore 
    });
  }
  
  // Process transfer...
});

Monitoring and Analytics

Track attestation metrics for security insights:

// Log attestation events
function logAttestation(decoded) {
  const event = {
    timestamp: new Date(),
    deviceId: decoded.sub,
    riskScore: decoded.deviceIntelligence.riskScore,
    jailbroken: decoded.deviceIntelligence.jailbreakDetected,
    tenantId: decoded.tenantId,
    permissions: decoded.permissions
  };
  
  // Send to your analytics service
  analytics.track('device_attestation', event);
  
  // Alert on high-risk devices
  if (event.riskScore > 75) {
    alerting.send('High risk device detected', event);
  }
}

Next Steps