Make your app enterprise ready
Give your own customers SAML or OIDC SSO, SCIM, phishing-resistant admin sign-in, an audit log, retention rules and legal hold, free in your app.
Answer an enterprise buyer's security questionnaire with features your app already has. Everything here runs in the free runtime, inside your app: it is what your app does for your customers, not a paid plan. The example customer is Pinecart's enterprise buyer, with the domain pinecart.com.
Before you start: a MainSDK app with sign-in on auth and data on db. See Sign-in and sessions.
1. Turn on SSO per customer organisation
SAML 2.0 and OIDC single sign-on ship in phase 1 (milestone 0.3), configured per customer organisation. Where your identity provider driver lacks SAML or SCIM, MainSDK provides it in your app on top of the driver.
auth:
driver: github
methods: [passkeys, sso]
sso: { protocols: [saml, oidc], scim: true }
admin: { roles: [admin, support], require: phishing-resistant }"use server";
import { auth } from "mainsdk";
export async function enablePinecartSso(clientSecret: string) {
await auth.sso.enable("pinecart", {
protocol: "oidc",
issuer: "https://pinecart.okta.com",
clientId: "pinecart-app",
clientSecret,
domains: ["pinecart.com"],
});
}For a customer on SAML, pass protocol: "saml" with the IdP's metadata and the same domains; see SAML in the auth interface for the two routes it needs. The IdP's certificate comes only from that metadata.
jane@pinecart.com is routed to Pinecart's identity provider by her verified domain. Unsigned assertions, the wrong audience, expired assertions and replayed assertion IDs are refused with MAIN-AUTH-0008.
2. Provision users with SCIM
import { auth } from "mainsdk";
const { endpoint, token } = await auth.scim.enable("pinecart", {
groupRoles: { "Pinecart Admins": "admin", "Pinecart Staff": "editor" },
});Give the endpoint and token to Pinecart's IT team. Removing someone in their directory ends that person's sessions in your app.
3. Require phishing-resistant admin sign-in
With auth.admin.roles set as in step 1, mainsdk check fails when one of those roles can sign in without passkeys or SSO with phishing-resistant MFA (rule auth.admin_phishing_resistant). That covers PCI DSS 4.0.1 req. 8.4.1.
4. Set roles and fine-grained access
access runs RBAC, ABAC and ReBAC on Cedar by default, or on the engine your customer already runs. See Permissions and Policy as code.
5. Keep an audit log
Every action and every decision is written to an append-only, tamper-evident audit log in OCSF.
logging:
audit: true
redact: [personal, sensitive, child, phi]
retention: 12mStreaming the audit log to each customer organisation's SIEM comes in phase 2. See Logs, audit and telemetry.
6. Set retention, deletion and legal hold
privacy:
retention: { personal: 365d, logs: 30d, analytics: 13m }
erase: { confirm-within: 30d }import { privacy } from "mainsdk";
declare const user: string; // the person the hold is for
await privacy.hold(user, { until: "2027-06-30", reason: "Pinecart litigation hold" });
const record = await privacy.erase(user, { reason: "customer request" }); // signed erasure recordA legal hold pauses erasure and retention for that person and is audited. See Privacy and data rights.
7. Keep data in the customer's region
Overlays by market keep each customer's data in its region: US, EU or India. See Data and regions.
8. Restrict outbound network and show what the app does
Buyers ask which outside services your app can reach and how they can see it. Allow only named hosts, IPs and ranges, and export the same list as platform rules: see Restrict which hosts and IPs your app can reach. Send the audit log, decisions and destinations to the buyer's tools: see See every call, decision and change your app makes.
9. Verify it worked
npx mainsdk checkThe check passes only when every admin role signs in with a phishing-resistant method. Then sign in as a test user on a verified domain and confirm you are sent to the customer's identity provider.
Later: IP allowlists and customer-managed keys
Inbound IP allowlists per customer organisation, so only Pinecart's office and VPN ranges can sign in to Pinecart's workspace, run through limits. How they are configured is not decided yet: it arrives with the limits spec. See Rate limits and quotas.
Customer-held keys ship in phase 3 (milestone 1.2):
import { crypto } from "mainsdk";
await crypto.keys.customer("pinecart", {
kms: "aws-xks",
keyArn: "arn:aws:kms:eu-west-1:111122223333:key/1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
});Pinecart's records are then wrapped by Pinecart's own key, through AWS XKS or Google External Key Manager. If Pinecart revokes its key, reads of its data fail closed; other customers are unaffected.
What MainSDK doesn't cover
Private networking to the customer, on-premise hosting of the app and contractual SLAs belong to whoever runs the app, and mainsdk check says so.
Next
Open questions
Was this page helpful?