Integrate: step-up
ReadyThe smallest possible integration: drop one signed approval in front of a single high-stakes action — a transfer, a deployment, an unlock — and change nothing about how your users log in. A short guard on the server, right before the operation runs.
Who this is for: a team that wants strong, phishing-resistant approval on specific actions without rebuilding their login. If you later want Ydentifi as your second factor everywhere, see Integrate: full.
Step-up or full?#
Step-up · this page
Add one signed approval before a specific high-stakes action. Your login is untouched. The smallest change — start here.
Full second factor
Make Ydentifi the second factor across enrollment and every sensitive action, with the tier chosen per action by your own policy. See Integrate: full.
First, onboard each user — once#
Step-up guards an action for a user Ydentifi already knows. Before the guard can run for someone, you onboard them a single time:
- Create an opaque identity and store its id next to your user. CreateUser returns a 64-character
opaque_id; save it as one more column in the database where you already keep that person. - Enroll their device. The user scans an enrolment QR to bind a phone that can approve challenges. See Enroll & link devices.
That opaque_id is the entire link between your world and Ydentifi’s — you keep the real person, Ydentifi only ever sees the token.
| name | … | ydentifi_opaque_id | |
|---|---|---|---|
| pete.mitchel@acme.example | Pete Mitchell | … | dkfjvfisdg8f7a1c9e2b… |
| jo.tan@acme.example | Jo Tan | … | 9b2e7c4a1f0d5583c410… |
Add one column. Ydentifi only ever sees the value in the highlighted column — the name, email, and everything else stay in your database, invisible to it.
Where it goes#
Nothing about how users authenticate to your product changes. You insert a single guard on the server, at the moment a sensitive action is about to run.
The guard, in code#
The whole integration is one function you call before the operation. It issues a challenge over the exact action, waits for the signed result, verifies it, and returns success only on a verified approval:
// install: go get github.com/ydentifi/go
// requireApproval blocks until the user signs off on THIS exact action —
// or denies it. Run it server-side, right before the operation executes.
func requireApproval(ctx context.Context, opaqueID, action string,
tier ydentifi.AuthStrength) error {
ch, err := client.IssueAuthChallenge(ctx, ydentifi.IssueAuthChallengeRequest{
OpaqueID: opaqueID,
Context: []byte(action), // the exact text the user sees and signs
TTLSeconds: 120,
MinStrength: tier, // required — the tier this action demands
})
if err != nil { return err }
// The challenge lands on the enrolled device (or render ch.QREncoded).
// Blocks until terminal; the deployment verifies and returns the verdict.
result, err := client.AwaitChallenge(ctx, ch.ChallengeID)
if err != nil { return err }
switch result.Status {
case ydentifi.ChallengeStatusApproved:
return nil // approved — the caller may proceed
case ydentifi.ChallengeStatusDenied:
return ErrDenied
default:
return ErrExpired // no answer within the TTL
}
}
// The call site. Your existing login and session checks are untouched —
// you only add the guard immediately before the sensitive operation.
func handleTransfer(w http.ResponseWriter, r *http.Request) {
// ... your existing auth / session checks ...
err := requireApproval(r.Context(), user.OpaqueID,
"Approve €4,200 transfer to ···· 8842",
ydentifi.AuthStrengthBiometric)
if err != nil {
http.Error(w, "not approved", http.StatusForbidden) // fail closed
return
}
doTransfer() // reached ONLY on a verified APPROVED
}The same shape works in every SDK — the full create→challenge→await→verify flow and per-language versions are in Your first ceremony and the API reference.
Choosing the tier#
Set the strength to match the stakes of this action — a signed biometric approval for most things, add location for the highest-value ones. You pass it per call, so different actions can demand different tiers. See Factors & levels.
Handle the outcome, fail closed#
There is exactly one path that lets the action run: the frame is APPROVED and its signature verifies. Everything else — denied, expired, a stream error, a bad signature — must block the operation. Treat “not a verified yes” as “no.”
The user needs an enrolled device first — step-up authorizes an action, it does not enroll. And keep one opaque identity per real person (see CreateUser): a challenge is delivered to that identity’s device, so a duplicate identity means the approval lands in the wrong place.