Authentication
Karrio Apps support multiple authentication methods to ensure secure access to platform resources and external services. This guide covers all authentication patterns and best practices.
Overview
Karrio Apps use a multi-layered authentication system:
- App Installation API Keys - Automatic API keys for each app installation
- OAuth2 Authorization - For external service integrations
- JWT Tokens - For embedded app authentication
- Session-based Auth - For user-context operations
App Installation API Keys
Every app installation automatically receives a unique API key that provides access to Karrio’s APIs with the app’s configured permissions.
Automatic API Key Generation
When an app is installed, Karrio automatically generates an API key:
1mutation InstallApp { 2 install_app( 3 input: { 4 app_id: "my-app" 5 app_type: "private" 6 access_scopes: ["manage_shipments"] 7 } 8 ) { 9 installation { 10 id 11 app_id 12 api_key # Automatically generated 13 access_scopes 14 is_active 15 } 16 } 17}
The API key format follows this pattern:
karrio_app_{app_id}_{random_suffix}
Using API Keys
Use the API key to authenticate requests to Karrio’s APIs:
Using fetch directly1const response = await fetch("/api/v1/shipments", { 2 headers: { 3 Authorization: `Bearer ${app_api_key}`, 4 "Content-Type": "application/json", 5 }, 6}); 7 8// Using the Karrio client 9import { createKarrioClient } from "@karrio/app-store"; 10 11const karrio = createKarrioClient(app_api_key); 12const shipments = await karrio.getShipments();
API Key Management
Karrio provides utilities to manage API keys:
Rotate API key1const rotateApiKey = async (installationId: string) => { 2 const result = await graphql( 3 ` 4 mutation RotateApiKey($id: String!) { 5 rotate_app_api_key(input: { id: $id }) { 6 installation { 7 id 8 api_key 9 } 10 } 11 } 12 `, 13 { id: installationId }, 14 ); 15 16 return result.rotate_app_api_key.installation.api_key; 17}; 18 19// Ensure API key exists 20const ensureApiKey = async (installationId: string) => { 21 const result = await graphql( 22 ` 23 mutation EnsureApiKey($id: String!) { 24 ensure_app_api_key(input: { id: $id }) { 25 installation { 26 id 27 api_key 28 } 29 } 30 } 31 `, 32 { id: installationId }, 33 ); 34 35 return result.ensure_app_api_key.installation.api_key; 36};
OAuth2 for External Services
For integrating with external services (like Shopify, WooCommerce, etc.), Karrio Apps support OAuth2 flows.
Setting Up OAuth2
Create an OAuth app for external service integration:
1mutation CreateOAuthApp { 2 create_oauth_app( 3 input: { 4 display_name: "Shopify Integration" 5 description: "Connect with Shopify stores" 6 launch_url: "https://myapp.com/shopify/launch" 7 redirect_uris: "https://myapp.com/shopify/callback" 8 features: ["oauth", "webhooks"] 9 } 10 ) { 11 oauth_app { 12 id 13 client_id 14 client_secret 15 redirect_uris 16 } 17 } 18}
OAuth2 Authorization Flow
Implement the standard OAuth2 authorization code flow:
1. Generate authorization URL1const generateAuthUrl = (shopDomain: string, clientId: string) => { 2 const params = new URLSearchParams({ 3 client_id: clientId, 4 scope: "read_orders,write_shipping", 5 redirect_uri: "https://myapp.com/shopify/callback", 6 state: generateSecureState(), 7 response_type: "code", 8 }); 9 10 return `https://${shopDomain}/admin/oauth/authorize?${params}`; 11}; 12 13// 2. Handle callback and exchange code for token 14export async function handleOAuthCallback(code: string, state: string) { 15 // Verify state parameter 16 if (!verifyState(state)) { 17 throw new Error("Invalid state parameter"); 18 } 19 20 // Exchange code for access token 21 const response = await fetch( 22 `https://${shopDomain}/admin/oauth/access_token`, 23 { 24 method: "POST", 25 headers: { "Content-Type": "application/json" }, 26 body: JSON.stringify({ 27 client_id: process.env.SHOPIFY_CLIENT_ID, 28 client_secret: process.env.SHOPIFY_CLIENT_SECRET, 29 code, 30 }), 31 }, 32 ); 33 34 const { access_token } = await response.json(); 35 36 // Store encrypted token in app metafields 37 await storeAccessToken(access_token); 38}
Storing OAuth Credentials
Use app metafields to securely store OAuth credentials:
1const storeAccessToken = async ( 2 accessToken: string, 3 installationId: string, 4) => { 5 await updateAppInstallation({ 6 id: installationId, 7 metafields: [ 8 { 9 key: "shopify_access_token", 10 value: accessToken, 11 type: "password", // Automatically encrypted 12 is_required: true, 13 is_sensitive: true, 14 }, 15 { 16 key: "shopify_shop_domain", 17 value: shopDomain, 18 type: "string", 19 is_required: true, 20 is_sensitive: false, 21 }, 22 ], 23 }); 24};
JWT Authentication for Embedded Apps
For embedded apps running within the Karrio dashboard, JWT tokens provide secure authentication.
JWT Token Structure
Karrio generates JWT tokens with this payload:
1{ 2 "iss": "karrio-dashboard", 3 "aud": "karrio-api", 4 "sub": "app-{app_id}-{installation_id}", 5 "exp": 1640995200, 6 "iat": 1640991600, 7 "app_id": "my-app", 8 "installation_id": "inst_12345", 9 "user_id": "user_67890", 10 "org_id": "org_54321", 11 "access_scopes": ["manage_shipments", "manage_data"] 12}
Using JWT Tokens
JWT tokens are automatically provided to embedded apps:
In your embedded app component1export default function MyApp({ app, context, jwt }: AppComponentProps) { 2 // JWT token is available for API requests 3 const makeAuthenticatedRequest = async (endpoint: string) => { 4 const response = await fetch(endpoint, { 5 headers: { 6 'Authorization': `Bearer ${jwt}`, 7 'Content-Type': 'application/json' 8 } 9 }); 10 11 return response.json(); 12 }; 13 14 return ( 15 <div> 16 {/* Your app UI */} 17 </div> 18 ); 19}
JWT Middleware
For server-side routes, Karrio provides JWT authentication middleware:
api/my-app/data/route.ts1import { NextRequest, NextResponse } from "next/server"; 2import { authenticateAppRequest } from "@karrio/app-store/auth"; 3 4export async function GET(request: NextRequest) { 5 try { 6 // Authenticate the request 7 const context = await authenticateAppRequest("my-app", request); 8 9 // Access authenticated context 10 const { karrio, installation, user } = context; 11 12 // Make authenticated API calls 13 const data = await karrio.getShipments(); 14 15 return NextResponse.json(data); 16 } catch (error) { 17 return NextResponse.json( 18 { error: "Authentication failed" }, 19 { status: 401 }, 20 ); 21 } 22}
Dual Authentication Strategy
Karrio Apps support both session-independent and session-based authentication:
Session-Independent (Primary)
For server-to-server communication and webhook handling:
Using app's own API key1const response = await fetch("/api/apps/my-app/webhook", { 2 method: "POST", 3 headers: { 4 Authorization: `Bearer ${app_api_key}`, 5 "X-App-API-Key": app_api_key, 6 "Content-Type": "application/json", 7 }, 8 body: JSON.stringify(webhookData), 9});
Session-Based (Fallback)
For user-context operations within the dashboard:
Dashboard makes request with user session1const response = await fetch("/api/apps/my-app/user-data", { 2 headers: { 3 Cookie: userSessionCookie, 4 "X-CSRF-Token": csrfToken, 5 }, 6});
Security Best Practices
1. Credential Storage
Always store sensitive credentials in encrypted metafields:
✅ Correct: Use metafields with encryption1await updateAppInstallation({ 2 id: installationId, 3 metafields: [ 4 { 5 key: "api_secret", 6 value: secretValue, 7 type: "password", // Automatically encrypted 8 is_sensitive: true, 9 }, 10 ], 11}); 12 13// ❌ Wrong: Don't store secrets in plain text 14const config = { 15 api_secret: secretValue, // Exposed in logs/database 16};
2. State Parameter Validation
Always validate OAuth state parameters:
1const generateState = () => { 2 return crypto.randomBytes(32).toString("hex"); 3}; 4 5const verifyState = (receivedState: string, expectedState: string) => { 6 return crypto.timingSafeEqual( 7 Buffer.from(receivedState), 8 Buffer.from(expectedState), 9 ); 10};
3. Token Expiration
Implement proper token expiration and refresh logic:
1const refreshAccessToken = async (refreshToken: string) => { 2 try { 3 const response = await fetch("/oauth/token", { 4 method: "POST", 5 headers: { "Content-Type": "application/json" }, 6 body: JSON.stringify({ 7 grant_type: "refresh_token", 8 refresh_token: refreshToken, 9 client_id: process.env.CLIENT_ID, 10 client_secret: process.env.CLIENT_SECRET, 11 }), 12 }); 13 14 const { access_token, expires_in } = await response.json(); 15 16 // Store new token with expiration 17 await storeAccessToken(access_token, Date.now() + expires_in * 1000); 18 19 return access_token; 20 } catch (error) { 21 // Handle refresh failure 22 throw new Error("Token refresh failed"); 23 } 24};
4. Request Validation
Validate all incoming requests with proper signatures:
1const validateWebhook = ( 2 payload: string, 3 signature: string, 4 secret: string, 5) => { 6 const expectedSignature = crypto 7 .createHmac("sha256", secret) 8 .update(payload) 9 .digest("hex"); 10 11 return crypto.timingSafeEqual( 12 Buffer.from(signature), 13 Buffer.from(expectedSignature), 14 ); 15};
Error Handling
Implement comprehensive error handling for authentication failures:
1const handleAuthError = (error: any) => { 2 switch (error.code) { 3 case "INVALID_TOKEN": 4 // Refresh token or re-authenticate 5 return refreshAuthentication(); 6 7 case "INSUFFICIENT_PERMISSIONS": 8 // Request additional permissions 9 return requestPermissions(); 10 11 case "RATE_LIMITED": 12 // Implement backoff strategy 13 return retryWithBackoff(); 14 15 default: 16 // Log error and notify user 17 console.error("Authentication error:", error); 18 throw new Error("Authentication failed"); 19 } 20};
Testing Authentication
Test your authentication flows thoroughly:
Test API key authentication1describe("API Key Authentication", () => { 2 it("should authenticate with valid API key", async () => { 3 const response = await fetch("/api/test", { 4 headers: { 5 Authorization: `Bearer ${validApiKey}`, 6 }, 7 }); 8 9 expect(response.status).toBe(200); 10 }); 11 12 it("should reject invalid API key", async () => { 13 const response = await fetch("/api/test", { 14 headers: { 15 Authorization: "Bearer invalid-key", 16 }, 17 }); 18 19 expect(response.status).toBe(401); 20 }); 21}); 22 23// Test OAuth flow 24describe("OAuth Flow", () => { 25 it("should complete OAuth authorization", async () => { 26 const authUrl = generateAuthUrl("test-shop", "client-id"); 27 expect(authUrl).toContain("oauth/authorize"); 28 29 const token = await handleOAuthCallback("auth-code", "valid-state"); 30 expect(token).toBeDefined(); 31 }); 32});
Next Steps
- Building Apps - Learn how to build your app with authentication
- API Integration - Integrate with Karrio’s APIs
- Examples - See authentication examples in real apps