Launch on 01/01/2027Licenses available from 01/01/2027
PreviewThis page documents the release that launches on 01/01/2027. Packages, images and sign-up open then, and details may change before it.
Docs / Guides / Integrate: step-up

Integrate: step-up

Ready
TL;DR

The 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:

  1. 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.
  2. 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.

your_users  ·  your existing database
emailname…ydentifi_opaque_id
pete.mitchel@acme.examplePete Mitchell…dkfjvfisdg8f7a1c9e2b…
jo.tan@acme.exampleJo 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.

Your loginunchanged
→
Sensitive actionuser asks to do the thing
→
Ydentifi guardchallenge → approve → verify
→
Proceed / denyonly on a verified yes

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.”

⚠ Before this works

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.

Opens on 01/01/2027

Available at launch

Until then, you can try the whole ceremony on your own phone.