Skip to content

Local signature validation vs. server-side exchange ​

This page explains the two mechanisms behind Wicket's three stateless verification packages, and how to choose based on the shape of the credential you have.

What the three packages have in common: they're plain Go packages that your application backend calls directly. They don't go through Wicket's middleware, don't read or write any store, and don't depend on the DI container. You pass in a credential string obtained by a mobile app or frontend, and get back a verified identity or a classifiable error.

The two mechanisms ​

Local signature validation (oidcverify): the credential is a signed, self-contained ID token (JWT). The backend verifies the signature locally using the JWKS public keys published by the provider, then checks claims such as iss, aud, and exp. The public keys are cached, so the vast majority of verifications need no outbound network access.

Server-side exchange (wechatverify, phoneverify): the credential is a short-lived opaque string that only the vendor can interpret (a WeChat authorization code, a number verification token). The backend attaches its own application credentials, sends the string to the vendor's API, and the vendor returns the identity. Every verification is a network round trip.

Comparison ​

DimensionLocal signature validation oidcverifyServer-side exchange wechatverify / phoneverify
Credential shapeSigned JWT carrying its own claimsOpaque short-lived string
Outbound network per verification?No; only on the first JWKS fetch, on an unknown kid, and on cache-expiry refreshYes, one round trip each time
Typical latencyLocal cryptography, sub-millisecondDepends on the network round trip to the vendor
Impact of a vendor outageUnaffected once public keys are cachedCan't verify while the vendor is unavailable
Secrets the backend must holdNone; only public keysYes: app secret / access key
Time checksYour side: inject a clock to validate exp / iat / nbfVendor side: the vendor decides whether the credential has expired
Quotas and rate limitsNoneSubject to the vendor's QPS and quotas; "retryable" is a routine branch
Verifying the same credential againPossible, with the same resultUsually not; credentials are mostly single-use, and a repeated exchange fails
Error style9 fine-grained sentinels (signature, expiry, issuer, audience, nonce…)4 category sentinels + *VendorError carrying the vendor's original code

How to choose ​

There's only one rule: look at the shape of the credential, not the vendor's name.

  • The credential is a signed JWT (Apple's identityToken, an ID token from the Google Sign-In SDK, an ID token from any standard OIDC provider) → use oidcverify. Even if the vendor also offers a "send me the token and I'll verify it" API, there's no need to use it: one more outbound call just turns the vendor's outages into your outages.
  • The credential is a vendor-private opaque string → server-side exchange is the only option, because nobody but the vendor can read it.

Conventions shared by both ​

Regardless of mechanism, all three packages take the same conservative stance:

  • Always fail closed. Any situation where the identity can't be confirmed returns an error; there's no "verification failed but here's a partial identity".
  • Construction fails when unconfigured. When required configuration is missing, NewVerifier returns an error immediately, rather than you finding out at the first verification. wechatverify / phoneverify return ErrUnavailable; oidcverify returns a plain error when the issuer or audience is empty.
  • Unreadable responses are treated as "rejected". In a server-side exchange, an unrecognized vendor return code is classified as ErrInvalidCredential rather than ErrRetryable—treating a rejection you can't read as retryable could retry it into an accidental pass.
  • Caller cancellation isn't retryable (wechatverify / phoneverify only). When ctx is canceled, the error satisfies errors.Is(err, context.Canceled) and carries no retryable marker; when the deadline expires, it satisfies both context.DeadlineExceeded and ErrRetryable. oidcverify doesn't preserve context errors: cancellation or timeout while fetching JWKS both surface as ErrKeyNotFound.
  • The packages don't log. Error values and the string forms of configuration are already scrubbed of secrets; logging is up to the caller.

Suggested response codes ​

The table below is one common way to map verification results to your own API responses, for reference:

ResultSuggested responseReason
Success200, continue the business flow—
ErrInvalidCredential, or any oidcverify sentinel other than ErrKeyNotFound401The credential the user submitted doesn't hold up
oidcverify.ErrKeyNotFound401 or 503Either the token's kid really doesn't exist, or fetching JWKS failed; the sentinel alone can't tell them apart, so alert and check the logs
ErrRetryable503, optionally with Retry-AfterCan't decide right now; the client may retry later
ErrUnavailable503, and alertA configuration or permission problem on your side, for operations to handle
ErrMalformedResponse502The vendor returned something unintelligible
context.CanceledNo response neededThe caller has given up

Typical mobile login sequence (server-side exchange) ​

text
App ──(vendor SDK)──> Vendor: user authorizes
App <──────────────── Vendor: one-time code / token
App ──(code)────────> Your backend
                      Your backend ──(code + app credentials)──> Vendor API
                      Your backend <──(unionid / phone number)── Vendor API
App <──(your session)── Your backend: find or create the user by identity

The app secret lives only on your backend (and in the process hosting Wicket). It doesn't need to be shipped to the app or distributed to every business system.

Released under the Apache License 2.0 · Documentation © 2026 Wicket authors