Security Best Practices

Protect your application and users with proper API key management, input validation, and secure data handling.

API Key Security

Public vs Secret Keys

The MIMS SDK uses two types of API keys:

  • pk_* - Public keys, safe for client-side use with limited scopes
  • sk_* - Secret keys, server-side only with full access

Never Expose Secret Keys

Secret keys (sk_*) should NEVER be used in client-side code, committed to version control, or exposed in browser network requests.
Safe API Key Usagetsx
// CORRECT: Public key in client-side code
<MIMSProvider apiKey={process.env.NEXT_PUBLIC_MIMS_API_KEY!}>
  <App />
</MIMSProvider>

// WRONG: Never use secret keys client-side
// <MIMSProvider apiKey={process.env.MIMS_SECRET_KEY!}> // DANGER!

// CORRECT: Secret keys in server-side API routes only
// app/api/documents/route.ts
export async function POST(request: Request) {
  const response = await fetch('https://api.mims-tech.com/documents', {
    headers: {
      'X-API-Key': process.env.MIMS_SECRET_KEY!, // Server-side only
    },
  });
  return Response.json(await response.json());
}

Environment Variables

.env.localbash
# Public key - safe for client-side (NEXT_PUBLIC_ prefix)
NEXT_PUBLIC_MIMS_API_KEY=pk_live_xxxxxxxxxxxx

# Secret key - server-side only (no NEXT_PUBLIC_ prefix)
MIMS_SECRET_KEY=sk_live_xxxxxxxxxxxx
.gitignoretext
# Ignore all environment files
.env
.env.local
.env.*.local

Input Validation

Always validate user input before processing or submission:

Input Validationtsx
import { z } from 'zod';

// Define validation schemas
const textFieldSchema = z.object({
  type: z.literal('text'),
  content: z.string()
    .min(1, 'Field is required')
    .max(1000, 'Maximum 1000 characters')
    .refine(
      (val) => !/<script/i.test(val),
      'Invalid content detected'
    ),
});

const signatureFieldSchema = z.object({
  type: z.literal('signature'),
  imageData: z.string()
    .startsWith('data:image/', 'Must be a valid image')
    .max(500000, 'Image too large'), // ~500KB limit
});

// Validate before setting field value
function validateAndSetFieldValue(
  fieldId: string,
  value: unknown,
  setFieldValue: (id: string, value: AnyFieldValue) => void
) {
  const field = useDocumentStore.getState().fields.get(fieldId);
  if (!field) return { success: false, error: 'Field not found' };

  try {
    let validated: AnyFieldValue;
    
    switch (field.definition.type) {
      case 'text':
        validated = textFieldSchema.parse(value);
        break;
      case 'signature':
        validated = signatureFieldSchema.parse(value);
        break;
      // ... other field types
      default:
        throw new Error('Unknown field type');
    }
    
    setFieldValue(fieldId, validated);
    return { success: true };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return { success: false, error: error.errors[0].message };
    }
    return { success: false, error: 'Validation failed' };
  }
}

Content Security

XSS Prevention

Safe Renderingtsx
// WRONG: Rendering user content as HTML
function UnsafeLabel({ field }: { field: FieldState }) {
  return <div dangerouslySetInnerHTML={{ __html: field.definition.label }} />;
}

// CORRECT: Render as text content
function SafeLabel({ field }: { field: FieldState }) {
  return <div>{field.definition.label}</div>;
}

// If you must render HTML, sanitize it first
import DOMPurify from 'dompurify';

function SanitizedContent({ html }: { html: string }) {
  const sanitized = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
    ALLOWED_ATTR: [],
  });
  return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}

Content Security Policy

next.config.jstypescript
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: [
      "default-src 'self'",
      "script-src 'self' 'unsafe-eval' 'unsafe-inline'", // Required for PDF.js
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: blob: https://cdn.mims-tech.com",
      "connect-src 'self' https://api.mims-tech.com",
      "frame-src 'none'",
      "object-src 'none'",
    ].join('; '),
  },
];

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: securityHeaders,
      },
    ];
  },
};

Secure Data Handling

Signature Data

Signature Securitytsx
// Clear signature data from memory after submission
async function submitAndCleanup() {
  const payload = getSubmissionPayload();
  
  try {
    await submitDocument(payload);
    
    // Clear sensitive data from store
    useDocumentStore.getState().reset();
    
    // Clear any canvas elements
    document.querySelectorAll('canvas').forEach(canvas => {
      const ctx = canvas.getContext('2d');
      ctx?.clearRect(0, 0, canvas.width, canvas.height);
    });
    
  } catch (error) {
    // Don't clear on error - user may need to retry
    throw error;
  }
}

// Don't log signature data
function logSubmission(payload: DocumentSubmissionPayload) {
  const sanitized = {
    ...payload,
    fields: payload.fields.map(f => ({
      ...f,
      value: f.type === 'signature' 
        ? { type: 'signature', imageData: '[REDACTED]' }
        : f.value,
    })),
  };
  console.log('Submission:', sanitized);
}

Local Storage

Storage Securitytsx
// DON'T store sensitive data in localStorage
// localStorage.setItem('apiKey', apiKey); // NEVER do this

// If you must cache document data, exclude signatures
function cacheDocumentMetadata(doc: DocumentWithFields) {
  const cacheable = {
    metadata: doc.metadata,
    fieldIds: doc.fields.map(f => f.id),
    // Don't cache field values or signatures
  };
  
  sessionStorage.setItem(
    `doc_${doc.metadata.id}`,
    JSON.stringify(cacheable)
  );
}

// Clear session data on logout
function clearSensitiveData() {
  sessionStorage.clear();
  useDocumentStore.getState().reset();
}

Network Security

HTTPS Enforcement

HTTPS Checktsx
// Verify HTTPS in production
if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined') {
  if (window.location.protocol !== 'https:') {
    console.error('MIMS SDK requires HTTPS in production');
    // Optionally redirect to HTTPS
    window.location.href = window.location.href.replace('http:', 'https:');
  }
}

Request Integrity

Request Securitytsx
// The SDK automatically includes integrity headers
// You can add additional security headers if needed

<MIMSProvider
  apiKey={process.env.NEXT_PUBLIC_MIMS_API_KEY!}
  customHeaders={{
    'X-Request-ID': generateRequestId(),
    'X-Client-Version': packageJson.version,
  }}
>
  <App />
</MIMSProvider>

Security Checklist

Before Going to Production

  • Using public keys (pk_*) in client-side code only
  • Secret keys are only used server-side
  • Environment variables are not committed to git
  • All user input is validated before processing
  • HTTPS is enforced in production
  • Content Security Policy headers are configured
  • Sensitive data is not logged or stored in localStorage
  • Error messages don't expose internal details

Security Reports

If you discover a security vulnerability, please email security@mims-tech.com. Do not disclose security issues publicly.

Related