Authentication
Qumra apps use OAuth 2.0 for authorization. The SDK handles the entire flow automatically.
OAuth Flow
The following diagram illustrates the complete OAuth 2.0 flow between the Qumra Admin Panel, your app, and the Qumra OAuth server:
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Qumra Admin │────>│ Your App │────>│ Qumra OAuth │
│ Panel │ │ /auth/* │ │ Server │
└─────────────┘ └──────────────┘ └─────────────┘
│ │ │
│ 1. Opens iframe │ │
│───────────────────>│ │
│ │ 2. Redirect to │
│ │ OAuth │
│ │───────────────────>│
│ │ │
│ │ 3. Authorization │
│ │ code callback │
│ │<───────────────────│
│ │ │
│ │ 4. Exchange code │
│ │ for token │
│ │───────────────────>│
│ │ │
│ │ 5. Access token │
│ │<───────────────────│
│ │ │
│ 6. App loaded │ 7. Store session │
│<───────────────────│ │
Step-by-Step
- Store owner opens your app from the Qumra admin panel.
- Your app calls
authenticate.admin(request)which checks for an existing session. - SDK redirects to OAuth if no valid session is found.
- User authorizes your app, and Qumra redirects back to your
callback_path. - SDK exchanges the authorization code for an access token.
- Session is stored in your database via Prisma.
- App loads with the authenticated session available.
Admin Authentication
The authenticate.admin() method is the primary way to authenticate requests in your app. It behaves differently for GET and POST requests.
- GET (Launch Flow)
- POST (Webhook Flow)
Use the loader to authenticate when the app is launched from the admin panel:
export async function loader({ request }: { request: Request }) {
const { admin, session, store } = await authenticate.admin(request);
// Use admin to make API calls
const data = await admin.graphql(`
query {
shop { name }
}
`);
return Response.json({ store, data });
}
The GET flow returns an AdminGetAuthResult:
interface AdminGetAuthResult {
admin: AdminGraphQLClient;
session: Session;
store: string;
data: JwtPayload;
}
| Field | Type | Description |
|---|---|---|
admin | AdminGraphQLClient | Pre-authenticated GraphQL client. |
session | Session | The current session from the database. |
store | string | The store domain (e.g. mystore.qumra.cloud). |
data | JwtPayload | Decoded JWT payload from the session token. |
Use the action to authenticate incoming webhook requests:
export async function action({ request }: { request: Request }) {
const { admin, session, payload, topic } = await authenticate.admin(request);
console.log(`Received ${topic} webhook`);
console.log("Payload:", payload);
// Process the webhook event
return Response.json({ success: true });
}
The POST flow returns an AdminPostAuthResult:
interface AdminPostAuthResult {
admin: AdminGraphQLClient;
session: Session;
store: string;
payload: unknown;
topic: string;
storeId: string;
}
| Field | Type | Description |
|---|---|---|
admin | AdminGraphQLClient | Pre-authenticated GraphQL client. |
session | Session | The current session from the database. |
store | string | The store domain. |
payload | unknown | The parsed webhook payload body. |
topic | string | The webhook topic (e.g. product/create). |
storeId | string | The unique identifier of the store that sent the event. |
Handle the Response Throw
Always handle the Response throw. During OAuth redirects, authenticate.admin() throws a Response object. Make sure to let it propagate or catch and re-throw it.
// DON'T do this -- it swallows the redirect
try {
const { admin } = await authenticate.admin(request);
} catch (error) {
// This catches the redirect Response!
return new Response("Error", { status: 500 });
}
// DO this -- let Response objects propagate
try {
const { admin } = await authenticate.admin(request);
} catch (error) {
if (error instanceof Response) throw error;
// Handle actual errors here
return new Response("Error", { status: 500 });
}
Next Steps
- Session Management -- Learn how sessions are stored and managed.
- Security -- Understand how the SDK protects your app.