Server Setup
Quick Start
Advanced Features
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);
}
}