Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/_data/flowIcons.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,33 +88,33 @@ export default {
<line x1="36" y1="39" x2="44" y2="39" stroke="#2f6b4f" stroke-width="1" stroke-dasharray="3,2"/>
</svg>`,

govIssue: `<svg viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
studentIdIssue: `<svg viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<rect x="18" y="48" width="44" height="20" rx="0" fill="none" stroke="#1b1a17" stroke-width="1.5"/>
<line x1="26" y1="48" x2="26" y2="68" stroke="#1b1a17" stroke-width="1.5"/>
<line x1="36" y1="48" x2="36" y2="68" stroke="#1b1a17" stroke-width="1.5"/>
<line x1="44" y1="48" x2="44" y2="68" stroke="#1b1a17" stroke-width="1.5"/>
<line x1="54" y1="48" x2="54" y2="68" stroke="#1b1a17" stroke-width="1.5"/>
<polygon points="12,48 40,22 68,48" fill="none" stroke="#1b1a17" stroke-width="1.5"/>
<text x="40" y="42" text-anchor="middle" font-size="9" fill="#1b1a17" font-family="Georgia,serif">&#x2605;</text>
<text x="40" y="62" text-anchor="middle" font-size="7" fill="#514c43" font-family="Georgia,serif" letter-spacing=".06em">DMV</text>
<text x="40" y="62" text-anchor="middle" font-size="7" fill="#514c43" font-family="Georgia,serif" letter-spacing=".06em">UNI</text>
<rect x="10" y="68" width="60" height="3" rx="0" fill="#1b1a17" opacity=".15"/>
</svg>`,

govHold: `<svg viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
studentIdHold: `<svg viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<rect x="26" y="10" width="28" height="50" rx="5" fill="#1b1a17"/>
<rect x="30" y="16" width="20" height="36" rx="2" fill="#f4f0e6"/>
<rect x="32" y="18" width="16" height="12" rx="1" fill="#ece6d6" stroke="#1b1a17" stroke-width=".75"/>
<rect x="33" y="20" width="5" height="7" rx="1" fill="#c9c0ab"/>
<line x1="40" y1="20" x2="46" y2="20" stroke="#c9c0ab" stroke-width="1"/>
<line x1="40" y1="23" x2="45" y2="23" stroke="#c9c0ab" stroke-width="1"/>
<line x1="40" y1="26" x2="46" y2="26" stroke="#c9c0ab" stroke-width="1"/>
<text x="47" y="30" font-size="5" fill="#c0341d" font-family="Georgia,serif">DL</text>
<text x="47" y="30" font-size="5" fill="#c0341d" font-family="Georgia,serif">ID</text>
<path d="M35,38 l5,-3 l5,3 v6 a5,5 0 0,1 -10,0 z" fill="none" stroke="#9a7b2e" stroke-width="1.5"/>
<circle cx="40" cy="43" r="1.5" fill="#9a7b2e"/>
<rect x="34" y="56" width="12" height="2" rx="1" fill="#c9c0ab"/>
</svg>`,

govDisclose: `<svg viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
studentIdDisclose: `<svg viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<rect x="8" y="22" width="36" height="44" rx="2" fill="#ece6d6" stroke="#c9c0ab" stroke-width="1" opacity=".7"/>
<line x1="14" y1="32" x2="38" y2="32" stroke="#c9c0ab" stroke-width="1"/>
<line x1="14" y1="38" x2="36" y2="38" stroke="#c9c0ab" stroke-width="1"/>
Expand All @@ -124,7 +124,7 @@ export default {
<line x1="24" y1="32" x2="14" y2="44" stroke="#c0341d" stroke-width="1" opacity=".4"/>
<text x="22" y="62" text-anchor="middle" font-size="7" fill="#c9c0ab" font-family="Georgia,serif" font-style="italic">full ID</text>
<rect x="36" y="30" width="38" height="26" rx="2" fill="#f4f0e6" stroke="#2f6b4f" stroke-width="2"/>
<text x="55" y="42" text-anchor="middle" font-size="9" fill="#2f6b4f" font-family="Georgia,serif" font-weight="bold">OVER 21</text>
<text x="55" y="42" text-anchor="middle" font-size="8" fill="#2f6b4f" font-family="Georgia,serif" font-weight="bold">ENROLLED</text>
<text x="55" y="52" text-anchor="middle" font-size="8" fill="#2f6b4f" font-family="Georgia,serif">&#x2713; verified</text>
</svg>`,

Expand Down
165 changes: 98 additions & 67 deletions src/verticals/education/build/issuer.njk
Original file line number Diff line number Diff line change
@@ -1,128 +1,159 @@
---
title: "Build an issuer — academic credentials"
description: "Issue diplomas over VCALM in 3 steps: start an exchange, deliver a signed VC, confirm. Server-side HTTP quick start."
description: "Issue diplomas as a VCALM coordinator: create an exchange, share an interaction URL, poll for the result. A workflow service does the protocol work."
permalink: /education/build/issuer/
---
{# Layer 4 — issuer coordinator quick start. The recommended path is the
@bedrock/vc-delivery server; the raw HTTP loop below shows what it runs. #}
{# Layer 4 — issuer coordinator. A coordinator is a CLIENT of a workflow
service; it does not run @bedrock/vc-delivery itself. It only creates
exchanges, hosts/shares interaction URLs, and polls for results. #}
<article class="prose">
<h1>Build an issuer</h1>
<p>
You run the university side: start an exchange and deliver a signed diploma
credential into the graduate's wallet — over the same VCALM exchange loop
the wallet speaks.
You run the university side. As a <strong>coordinator</strong> you speak
VCALM — just the coordinator side of it. You don't run <em>VCALM
delivery</em>, the on-the-wire exchange with the graduate's wallet; a
<em>workflow service</em> does that. Your whole job is three calls: create an
exchange, share an interaction URL that points the graduate's wallet at it,
then poll the exchange until the diploma has been issued.
</p>

<aside class="recommended">
<p class="kicker">Recommended</p>
<h2>Run the coordinator with @bedrock/vc-delivery</h2>
<h2>Drive a workflow service — don't build the protocol</h2>
<p>
You don't have to build the exchange loop by hand. Digital Bazaar's
The protocol mess — VCALM delivery, OID4VCI, OID4VP, and interoperating with
whatever wallet a graduate brings — is handled by a <em>workflow
service</em>. Digital Bazaar's
<a href="https://github.com/digitalbazaar/bedrock-vc-delivery">@bedrock/vc-delivery</a>
runs it for you today and is the supported path for issuers.
is one. <strong>The workflow service runs that library; you don't.</strong>
</p>
<p><code class="install">npm install @bedrock/vc-delivery</code></p>
<p>
You define a <em>workflow</em> (a reusable issuance config) and the module
handles each <em>exchange</em>. It serves VCALM (<code>vcapi</code>) and
also speaks OID4VCI and OID4VP, so one workflow delivers credentials over
whichever protocol a wallet selects.
A goal of VCALM is to let you use a workflow service off the shelf. An
administrator loads the <em>workflows</em> you need (a reusable issuance
config — what to issue, what to ask for). From then on, whenever a graduate
action requires a credential exchange, you create an exchange against that
workflow and read back the result.
</p>
</aside>

<h2>Under the hood: the raw VCALM exchange</h2>
<p>
Want to build your own coordinator, or just see exactly what the server does
on the wire? It's a short HTTP loop — three things:
</p>
<h2>What the coordinator does</h2>
<ol>
<li>Expose an interaction URL (in a QR code or link) that advertises
<code>vcapi</code>.</li>
<li>When the wallet POSTs to your exchange URL, respond with the
diploma in a <code>verifiablePresentation</code>.</li>
<li>Optionally ask the wallet to authenticate first (DID Auth), then
deliver; finish the exchange.</li>
<li><strong>Create an exchange</strong> by POSTing the workflow's variables
to its <code>exchanges</code> endpoint.</li>
<li><strong>Host and share an interaction URL</strong> that points the
graduate's wallet at that exchange.</li>
<li><strong>Poll the exchange</strong> until it completes, then read the
result.</li>
</ol>
<p>
The workflow service does everything in between — protocol negotiation,
DID authentication, signing, talking to whatever wallet shows up — and hands
you back just the result.
</p>

<h2>1. Advertise the exchange</h2>
<h2>1. Create the exchange</h2>
<p>
The wallet dereferences your interaction URL (<code>iuv=1</code>) and you
return the protocols you support. Offer <code>vcapi</code> for VCALM:
POST to your workflow's <code>exchanges</code> endpoint. The body carries a
<code>ttl</code> and the <code>variables</code> the workflow expects — here,
who the diploma is for:
</p>
{% highlight "bash" %}
POST https://workflows.example/workflows/diploma-issuance/exchanges
{% endhighlight %}
{% highlight "json" %}
{
"protocols": {
"vcapi": "https://issuer.university.example/exchanges/diploma-789"
"ttl": 900,
"variables": {
"subject": "did:example:graduate123",
"degree": "B.Sc. Computer Science"
}
}
{% endhighlight %}
<p>
This call is authenticated (your service uses a capability or OAuth2 token).
On success the service returns <code>204 No Content</code> with a
<code>Location</code> header — that's your exchange URL:
</p>
{% highlight "http" %}
HTTP/1.1 204 No Content
Location: https://workflows.example/workflows/diploma-issuance/exchanges/z19xQ...
{% endhighlight %}

<h2>2. Deliver the credential</h2>
<h2>2. Host and share the interaction URL</h2>
<p>
The wallet POSTs an empty body to start. You respond with the signed diploma
wrapped in a <code>verifiablePresentation</code>:
The exchange URL <em>is</em> the <code>vcapi</code> interaction endpoint.
Hosting and sharing it is the coordinator's responsibility — put it in a QR
code or a link, typically wrapped in a presentation request the wallet
understands:
</p>
{% highlight "json" %}
{
"verifiablePresentation": {
"@context": ["https://www.w3.org/ns/credentials/v2"],
"type": ["VerifiablePresentation"],
"verifiableCredential": [{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://w3id.org/education/v2"
],
"type": ["VerifiableCredential", "UniversityDegreeCredential"],
"issuer": "did:web:university.example",
"credentialSubject": {
"id": "did:example:graduate123",
"degree": {"type": "BachelorDegree", "name": "B.Sc. Computer Science"}
},
"proof": { "...": "issuer signature" }
}]
"VerifiablePresentation": {
"interact": {
"service": [{
"type": "VerifiableCredentialApiExchangeService",
"serviceEndpoint": "https://workflows.example/workflows/diploma-issuance/exchanges/z19xQ..."
}]
}
}
}
{% endhighlight %}
<p>
The exchange is complete when you return no
<code>verifiablePresentationRequest</code>.
The graduate's wallet POSTs to that endpoint and runs the exchange with the
workflow service directly. DID authentication and credential delivery happen
there — you are not in that loop.
</p>

<h2>3. (Optional) Authenticate the holder first</h2>
<h2>3. Poll for the result</h2>
<p>
To bind the diploma to a holder, respond to the wallet's first POST with a
<code>verifiablePresentationRequest</code> for DID Authentication, then
deliver on the next turn:
While the wallet works, GET the exchange URL (authenticated) until its
<code>state</code> is <code>complete</code>:
</p>
{% highlight "bash" %}
GET https://workflows.example/workflows/diploma-issuance/exchanges/z19xQ...
{% endhighlight %}
{% highlight "json" %}
{
"verifiablePresentationRequest": {
"query": [{
"type": "DIDAuthentication",
"acceptedMethods": [{"method": "example"}]
}],
"challenge": "99612b24-63d9-11ea-b99f-4f66f3e4f81a",
"domain": "issuer.university.example"
"exchange": {
"id": "z19xQ...",
"state": "complete",
"variables": {
"results": {
"didAuthn": {
"did": "did:example:graduate123",
"verifiablePresentation": { "...": "the wallet's authenticated VP" }
}
}
}
}
}
{% endhighlight %}
<p>
Verified per-step results land under
<code>exchange.variables.results</code>, keyed by the workflow's step names.
A <code>complete</code> state means the diploma was issued into the
graduate's wallet.
</p>

<h2>That's it</h2>
<p>
A conformant education issuer is this exchange: advertise, deliver, confirm.
The signing of the credential itself is your issuer instance's job — the
coordinator just runs the exchange.
An education issuer, as a coordinator, is these three calls: <strong>create
an exchange, host and share the interaction URL, poll for the result.</strong>
Signing the diploma and speaking every wallet's protocol is the workflow
service's job — you just create exchanges and read what comes back.
</p>

<p class="future">
On the wallet end, a standalone client-side VCALM library is still planned;
until it ships, the raw HTTP flow is the supported path there. For the
issuer/coordinator side shown here, use
until it ships, wallets run the exchange over the raw HTTP flow. The
coordinator side shown here works today against any VCALM workflow service,
such as one running
<a href="https://github.com/digitalbazaar/bedrock-vc-delivery">@bedrock/vc-delivery</a>.
</p>

<h2>Go deeper</h2>
<ul>
<li><a href="https://github.com/digitalbazaar/bedrock-vc-delivery">@bedrock/vc-delivery — workflow service</a></li>
<li><a href="https://www.w3.org/TR/vcalm/#did-authentication">VCALM: DID Authentication</a></li>
<li><a href="https://www.w3.org/TR/vc-data-model-2.0/">W3C Verifiable Credentials Data Model 2.0</a></li>
<li><a href="https://www.w3.org/TR/vcalm/">VCALM specification</a></li>
Expand Down
Loading