Quick start — Login with Valyd
Agent Quick-Start
- Source URL: https://docs.valyd.work/docs/quick-start
- Credentials / env vars needed: VALYD_CLIENT_ID, VALYD_CLIENT_SECRET, VALYD_REDIRECT_URI
- Files an integrator edits: .env, server route handlers (e.g. server.ts: /login and /callback)
- Estimated steps: 6
- Can complete without human input: NO — the client ID, client secret, and registered redirect URI must be created/copied by a human from the Valyd developer portal, and scopes must be enabled there.
- Prerequisites:
- Node.js 18+ installed.
- A registered app in the Valyd developer portal with a client ID and client secret.
- The redirect URI (e.g. http://localhost:8080/callback ) registered in the portal, matching exactly (no trailing slash).
- The scopes you intend to request (profile, verifications, doctor_license, zkp, mcp) enabled in the portal.
Prerequisites
- Node.js 18+ (the SDK requires it).
@valyd/sdk(latest —npm i @valyd/sdk).- A Valyd app with credentials. Obtain
VALYD_CLIENT_IDandVALYD_CLIENT_SECRETfrom the Valyd developer portal (get these from the developer portal → your project → Credentials).VALYD_CLIENT_SECRETis server-side only and must never reach a browser. - A redirect URI registered in the portal that exactly matches
VALYD_REDIRECT_URI(no trailing slash). - Scopes enabled in the portal: any of
profile,verifications,doctor_license,zkp,mcp.
Steps
-
Install the SDK. Run the install command (the official
@valyd/sdkhandles the full TPSSO/OAuth2 flow, login sessions for CSRF protection, and typed resource calls).npm install @valyd/sdkExpected output: npm adds
@valyd/sdk(a0.2.xor newer release) todependenciesinpackage.jsonand reports the package was added with no error exit code. -
Create the
.envfile. UseKEY=valuewith no spaces around=. The values shown are examples — substitute your own.# .env — no spaces around = VALYD_CLIENT_ID=9357c59bc1794b4c9efe8823e5878147 VALYD_CLIENT_SECRET=sk_live_a1b2c3d4e5f6... VALYD_REDIRECT_URI=http://localhost:8080/callbackVALYD_CLIENT_ID— get this from the Valyd developer portal → your project → Credentials.VALYD_CLIENT_SECRET— get this from the Valyd developer portal → your project → Credentials. Server-side only; never bundle into the browser.VALYD_REDIRECT_URI— must match the value registered in the portal exactly (no trailing slash). For local dev use e.g.http://localhost:8080/callbackand also register it in the portal.
Expected output: A
.envfile on disk with the three keys set. No command output; verify the file contents are correct. -
Initialize the client. Construct a
ValydClientfrom your environment variables.baseUrldefaults tohttps://idp.valyd.work.// server.ts import { ValydClient } from "@valyd/sdk"; const valyd = new ValydClient({ clientId: process.env.VALYD_CLIENT_ID!, clientSecret: process.env.VALYD_CLIENT_SECRET!, redirectUri: process.env.VALYD_REDIRECT_URI!, // e.g. http://localhost:8080/callback // baseUrl defaults to https://idp.valyd.work });Expected output: A configured
valydclient instance. No network call is made at construction. -
Start a login session and redirect to Valyd. Before redirecting the user, call
createLoginSession(). This issues an HMAC-signed marker that you must store server-side (here, anhttpOnlycookie). Then build the authorize URL withgetAuthorizationUrl()and redirect.// 1. Start a login session before redirecting the user. // This issues an HMAC-signed marker you must store server-side. app.get("/login", async (req, res) => { const session = await valyd.createLoginSession(); // Persist the marker (httpOnly cookie or server session). res.cookie("valyd_login", session.marker, { httpOnly: true, sameSite: "lax", secure: process.env.NODE_ENV === "production", maxAge: 10 * 60 * 1000, // 10 minutes }); // 2. Build the authorize URL and redirect. const url = valyd.getAuthorizationUrl({ state: session.authorizeState, scope: ["profile", "verifications"], productName: "My App", }); res.redirect(url); });Expected output:
createLoginSession()returns an object containingauthorizeStateandmarker. The browser is redirected (HTTP 302) to the Valyd authorize URL, and thevalyd_logincookie is set with the marker. -
Handle the callback and verify the login session (CSRF check). On the callback route, parse the query, then verify the stored marker — NOT the callback
state.// 3. Handle the callback. app.get("/callback", async (req, res) => { const { code, error } = valyd.parseCallback(req.url); if (error || !code) return res.status(400).send(error ?? "missing code"); // 4. CSRF check — verify the marker we stored, NOT the callback state. const marker = req.cookies.valyd_login; const check = await valyd.verifyLoginSession(marker); if (!check.valid) return res.status(400).send("Invalid login session"); });Expected output:
parseCallback(req.url)returns{ code, error }. On a valid login,verifyLoginSession(marker)returns{ valid: true }. If the marker is expired, missing, or tampered, it returns{ valid: false }and the route responds HTTP 400"Invalid login session".IF the callback contains an
erroror nocode: → respond HTTP 400 with the error message (or"missing code") and stop. IFverifyLoginSession(marker)returns{ valid: false }: → respond HTTP 400"Invalid login session"and stop; do NOT exchange the code. IF you are tempted to compare the sentstateto the callbackstate: → do NOT. Valyd does not echo yourstate; the callbackstateis Valyd’s own session id. UseverifyLoginSession(marker)instead. See login-sessions.md. -
Exchange the code for tokens and call resource endpoints. After the CSRF check passes, exchange the authorization code and use the access token to fetch user data.
// 5. Exchange the code for tokens. const tokens = await valyd.exchangeCode(code); // 6. Call resource endpoints with the access token. const profile = await valyd.getUserInfo(tokens.accessToken); const verifications = await valyd.getVerifications(tokens.accessToken); res.clearCookie("valyd_login"); // ...set your own app session and redirect the user.Expected output:
exchangeCode(code)returns a tokens object containingaccessToken.getUserInfo(tokens.accessToken)returns the user’s profile andgetVerifications(tokens.accessToken)returns their verifications. Thevalyd_logincookie is cleared. You then set your own app session and redirect the user.
The flow at a glance
| Step | What | SDK method |
|---|---|---|
| 1 | Start login, store marker | createLoginSession() |
| 2 | Redirect to Valyd | getAuthorizationUrl() |
| 3 | Read callback query | parseCallback() |
| 4 | CSRF check | verifyLoginSession(marker) |
| 5 | Get tokens | exchangeCode(code) |
| 6 | User data | getUserInfo(), getVerifications(), … |
Environment & app setup
| Item | Rule |
|---|---|
VALYD_CLIENT_ID | From the dev portal. |
VALYD_CLIENT_SECRET | Server-side only. Never bundle into the browser. |
VALYD_REDIRECT_URI | Must match the portal value exactly (no trailing slash). |
| Local dev | e.g. http://localhost:8080/callback — also register in the portal. |
| Scopes | Enable in the portal: profile, verifications, doctor_license, zkp, mcp. |
Verification
-
Confirm the SDK installed at the required version:
npm ls @valyd/sdkExpected output: a line like
@valyd/sdk@1.5.x(or newer). -
Run the full round trip: start the server, visit the
/loginroute in a browser, complete login at Valyd, and confirm the/callbackroute reaches step 6. A successful run sets thevalyd_logincookie on/login, passesverifyLoginSessionon the callback, and returns a populatedprofilefromgetUserInfo.
Common errors
-
Comparing OAuth
statefor CSRF (the most common mistake).- Cause: Valyd (TPSSO) does not echo the
stateyou send onauthorize; thestateon the callback is Valyd’s own session id, sosentState !== callbackStateis always “broken” and rejects valid logins. - Fix: Do not compare states. Use
createLoginSession()before redirect andverifyLoginSession(marker)on the callback for CSRF. See login-sessions.md.
- Cause: Valyd (TPSSO) does not echo the
-
“Invalid login session” on the callback.
- Cause: The marker cookie is missing (cookie not set, blocked, or expired beyond the 10-minute TTL) or tampered, so
verifyLoginSession(marker)returns{ valid: false }. - Fix: Ensure the
valyd_logincookie is set on/loginwithmaxAge: 10 * 60 * 1000andhttpOnly: true, that the browser sends it back on/callback, and that the user completes login within 10 minutes.
- Cause: The marker cookie is missing (cookie not set, blocked, or expired beyond the 10-minute TTL) or tampered, so
-
Redirect URI mismatch / scope errors at the authorize step.
- Cause:
VALYD_REDIRECT_URIdoes not exactly match the value registered in the portal (e.g. a trailing slash), or a requested scope is not enabled in the portal. - Fix: Make
VALYD_REDIRECT_URIidentical to the registered value (no trailing slash), and enable each requested scope (profile,verifications,doctor_license,zkp,mcp) in the portal before requesting it.
- Cause: