Step 1 - Sign in as Company Admin: Open Last1 ID and choose Company Admin entry.
Step 2 - Register app client: Go to App clients, click Register app, enter app name + app ID, then click Register.
Step 2a - Copy secret now: After register succeeds, a modal shows Client secret once. Copy it immediately into your secret manager/deployment env as LAST1_CLIENT_SECRET.
Step 2b - Lost the secret? In App clients, click the Regenerate client secret icon for that app, copy the new value, and replace the old secret in your deployment env.
Step 3 - Configure integration: Go to Integrations, select your app client, set Issuer URL, callback Redirect URIs, required scopes, and consent copy.
Step 4 - Use explicit callback path: Redirect URI must be a callback route such as https://<YOUR_APP_DOMAIN>/auth/callback (not root /).
Step 5 - Add app environment variables: In your deployment platform (Vercel/Railway/Netlify/etc.), add the LAST1_* values shown below.
Step 6 - Add login button: Put Continue with Last1 ID on your sign-in page using the login-button package.
Step 7 - Exchange code on backend: Handle callback server-side and call exchangeCode(); do not expose client secret in frontend bundles.
Step 8 - Run verification: Back in Integrations, click Run verification until all checks are green.
Step 9 - Promote + monitor: Provision dev/staging/prod clients, run readiness checks, then monitor observability and scheduler cards.
LAST1_ISSUER_URL: from Integrations to Issuer URL (usually https://last1.id).
LAST1_CLIENT_ID: from App clients / Self-serve onboarding card (Client ID field).
LAST1_CLIENT_SECRET: created during App clients → Register app → Register and shown once in the success modal; if lost, use Regenerate client secret on that app row.
LAST1_REDIRECT_URI: one explicit callback URI you configured (for example https://your-app.com/auth/callback).
Where to place them: your deployment platform env settings (Vercel/Railway/Render/Netlify) and local .env for development.
Discovery URL: https://last1.id/.well-known/openid-configuration
JWKS URL: https://last1.id/.well-known/jwks.json
Expected: discovery issuer exactly https://last1.id.
Expected: signing alg includes RS256 and JWKS contains at least one key.
http://localhost:3000/auth/callback https://app.example.com/auth/callback https://staging.example.com/auth/callback
Org admins should register callback URLs that match their own platform routes and environments.
npm install @last1id/sdk@0.1.0 @last1id/login-button@0.1.1 @last1id/evidence-sdk@0.1.0 # Set these in your deployment environment: LAST1_ISSUER_URL=https://last1.id LAST1_CLIENT_ID=<YOUR_LAST1_CLIENT_ID> LAST1_CLIENT_SECRET=<YOUR_LAST1_CLIENT_SECRET> LAST1_REDIRECT_URI=https://<YOUR_APP_DOMAIN>/<YOUR_CALLBACK_PATH>
Add these values to your deployment provider or `.env` file for your own environment. Use @last1id/evidence-sdk from your backend when you want to sync app activity as portable trust evidence for users.
import { Last1LoginButton } from "@last1id/login-button";
<Last1LoginButton
issuer="https://last1.id"
clientId={process.env.NEXT_PUBLIC_LAST1_CLIENT_ID!}
redirectUri="https://app.example.com/auth/callback"
scopes={["openid", "email", "profile", "offline_access"]}
/>Keep client secret server-side. For SPA, exchange code on backend. The hosted button defaults to the official Last1 icon pulled from https://last1.id/last1-mark.png so org admins do not need to manage logo assets manually.
import { createPkcePair, buildAuthorizeUrl, exchangeCode, refreshToken } from "@last1id/sdk";
const pkce = await createPkcePair();
const url = buildAuthorizeUrl({
issuerUrl: process.env.LAST1_ISSUER_URL!,
clientId: process.env.LAST1_CLIENT_ID!,
redirectUri: process.env.LAST1_REDIRECT_URI!,
scopes: ["openid", "email", "profile", "offline_access"]
}, pkce);
// callback:
await exchangeCode({
issuerUrl: process.env.LAST1_ISSUER_URL!,
clientId: process.env.LAST1_CLIENT_ID!,
clientSecret: process.env.LAST1_CLIENT_SECRET!,
redirectUri: process.env.LAST1_REDIRECT_URI!,
code,
codeVerifier
});// Example callback route (server-side exchange)
import { exchangeCode } from "@last1id/sdk";
export async function handleLast1Callback(req, res) {
const { code } = req.query;
const codeVerifier = req.session.last1CodeVerifier;
if (!code || !codeVerifier) return res.status(400).send("Missing code/codeVerifier");
const tokenSet = await exchangeCode({
issuerUrl: process.env.LAST1_ISSUER_URL!,
clientId: process.env.LAST1_CLIENT_ID!,
clientSecret: process.env.LAST1_CLIENT_SECRET!,
redirectUri: process.env.LAST1_REDIRECT_URI!,
code: String(code),
codeVerifier: String(codeVerifier),
});
// Map Last1 identity to your app user session here.
req.session.user = { last1Sub: tokenSet.id_token };
return res.redirect("/app");
}Keep LAST1_CLIENT_SECRET server-only. Never ship it to browser code.
1. Register app client in `App clients`.
2. Configure issuer URL, redirect URIs, scopes, and consent text in `Integrations`.
3. Run integration verification and promotion readiness checks.
4. Provision dev/staging/prod app clients and store generated secrets securely.
5. Monitor observability and scheduler health.
6. Configure retention, export audits, and schedule compliance bundles.
7. Download the "admin handoff" guide from Integrations and send it to partner engineering/admin teams.
- HTTPS issuer and redirect allowlists (localhost only for local dev)
- `openid` scope present
- Consent copy is explicit and user-readable
- OIDC discovery and JWKS endpoints healthy
- PKCE and nonce validated on callback
- Client secrets never shipped to browser bundles