@akku-work/consent-sdk-auth
v0.1.0-beta.1
Published
Akku Consent - zero-dependency SDK for authenticated web applications. Identity travels only inside a host-signed subject token.
Readme
@akku-work/consent-sdk-auth
Consent and preference management for an authenticated web application. Zero runtime dependencies.
This is the authenticated plane. Use it when the person whose consent you are recording is signed
in to your app and you can prove who they are. If you need a cookie banner for anonymous visitors,
use @akku-work/consent-sdk instead — the
two are separate packages with separate release cadences, and they are not interchangeable.
This package is headless: it decides, it does not draw. It resolves each published purpose to a
legal basis, a validity window and an outcome, and leaves rendering to you. For ready-made React
components, add
@akku-work/consent-sdk-react.
Install
npm install @akku-work/consent-sdk-authUsage
import { ConsentManager } from "@akku-work/consent-sdk-auth";
const consent = new ConsentManager({
apiHost: "https://consent.nova.example",
siteKey: "sk_live_9f2c…",
applicationId: "nova-bank-web",
getSubjectToken: () => session.fetchConsentToken(),
});
const states = await consent.getPurposeStates();
for (const state of states) {
// "disclose" | "ask" | "silent" | "re-ask" | "re-confirm"
console.log(state.key, state.outcome, state.granted);
}
// Only the purposes that still need a surface, in the order they were published.
const toAsk = await consent.purposesToAsk();There is no userId parameter
Identity travels only inside the host-signed subject token returned by getSubjectToken(). The
API has no option to pass a subject identifier directly, and this is not an oversight.
A user id handed over from the browser is a claim, not a credential — anyone with devtools open
can pass a different one. The backend derives the authenticated subject itself by verifying the
token's signature, exp, aud and iss; a body-supplied user id on this plane is rejected
outright rather than silently preferred. If you find yourself wanting to pass one, the token wiring
is what needs fixing.
getSubjectToken may be sync or async and is called fresh before every request, so refresh and
rotation stay entirely yours.
The five outcomes
getPurposeStates() is the whole point of this package. Each purpose resolves to one outcome,
and rendering from outcome rather than from granted is what keeps two opposite mistakes from
creeping in — needlessly re-prompting someone who already answered, and treating a stale consent as
current.
| Outcome | Meaning |
| ------------ | -------------------------------------------------------------- |
| disclose | Disclosed, never prompted — a necessary or legal_obligation basis. |
| ask | Nothing on file. Ask. |
| silent | Decided, still fresh, under the current policy. Say nothing. |
| re-ask | Decided but past its validity window. Ask again as a NEW decision. |
| re-confirm | Decided under a policy version since superseded. Ask again as a re-confirmation. |
"Granted but lapsed" and "granted under a replaced policy" are both granted: false with an
outcome that says why. Collapsing them loses the distinction that matters.
API
| Member | Purpose |
| --------------------------------- | ---------------------------------------------------- |
| getPurposeStates(now?) | Every published purpose joined with its outcome. |
| purposesToAsk(keys?) | Only the purposes that still need a surface. |
| hasConsent(key) | Is this purpose granted right now? |
| getPurposes() | The published purpose list, unmodified. |
| getConsentState() | The raw decision map. |
| getPreferences() | Preference-centre state. |
| grantConsent(id) | Grant one purpose. |
| updateConsent(decisions) | Record a per-purpose decision map. |
| recordDecisions(decisions) | Record decisions taken from a surface. |
| savePreferences(decisions) | Persist a preference-centre submission. |
| withdrawConsent(id?) | Withdraw one purpose, or all of them. |
| onChange(listener) | Subscribe; returns an unsubscribe function. |
| request<T>(path, init?) | Authenticated call against the Akku API. |
resolvePurposeStates, basisOf and purposesNeedingDecision are also exported as pure functions,
for deciding against purposes and a stored record you already hold.
Optional UI helpers
import { … } from "@akku-work/consent-sdk-auth/ui";A separate entry point on purpose. An app that never imports it pays zero bundle cost — index.ts
never reaches into it.
Licence
Proprietary. See LICENSE — publication on npm grants no licence to use this software.
