Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5cd08fe
fix: auth code flow alpha
auer-martin Aug 30, 2024
b64bce3
Merge branch 'main' of github.com:openwallet-foundation/credo-ts
auer-martin Aug 30, 2024
80b55b4
fix: make eslint happy
auer-martin Sep 11, 2024
9ccb645
feat: add method to test auth code flow
auer-martin Sep 11, 2024
8ab5b22
Merge remote-tracking branch 'upstream/main' into auth-code-flow-2
TimoGlastra Oct 9, 2024
bd02008
Merge branch 'main' into auth-code-flow-2
TimoGlastra Oct 9, 2024
b2eb533
fix: host separate oauth-auhtorization-server
TimoGlastra Oct 9, 2024
3de4e82
temp
TimoGlastra Oct 29, 2024
c0b49dc
temp
TimoGlastra Oct 30, 2024
be9876b
some updates
TimoGlastra Nov 5, 2024
9e1050b
Merge remote-tracking branch 'upstream/main' into auth-code-flow-2
TimoGlastra Nov 5, 2024
c7f9eb3
feat: working and tests passing
TimoGlastra Nov 8, 2024
ef57b9d
Merge remote-tracking branch 'upstream/main' into auth-code-flow-2
TimoGlastra Nov 8, 2024
bee17c8
docs(changeset): feat(openid4vc): oid4vci authorization code flow, pr…
TimoGlastra Nov 8, 2024
4b9e845
both token introspection and jwt flow working
TimoGlastra Nov 8, 2024
5545919
feat: presentation during issuance server side
TimoGlastra Nov 9, 2024
35c2c96
fix: loading of verifier module
TimoGlastra Nov 9, 2024
28a63dc
lot of fixes
TimoGlastra Nov 16, 2024
b1ef329
address feedback
TimoGlastra Nov 16, 2024
10c3403
docs(changeset): fix(openid4vc): use `vp_formats` in client_metadata …
TimoGlastra Nov 16, 2024
8b37fc7
docs(changeset): feat(openid4vc): support jwk thumbprint for openid t…
TimoGlastra Nov 16, 2024
eddaf68
address feedback
TimoGlastra Nov 16, 2024
5f039c8
remove fixme/todo
TimoGlastra Nov 16, 2024
d4c27f6
update lockfile
TimoGlastra Nov 16, 2024
317a005
fix lint
TimoGlastra Nov 16, 2024
979e268
fix coverage upload
TimoGlastra Nov 16, 2024
e23a394
codecov
TimoGlastra Nov 16, 2024
0190db4
fix type
TimoGlastra Nov 16, 2024
b01885a
some improvements
TimoGlastra Nov 17, 2024
899c89a
update
TimoGlastra Nov 17, 2024
e644178
feat(demo): ngrok and presentation during issuance
TimoGlastra Nov 18, 2024
69083cd
type
TimoGlastra Nov 19, 2024
08be88e
test: fix
TimoGlastra Nov 19, 2024
38a6665
test: fix
TimoGlastra Nov 19, 2024
a9f32da
Merge remote-tracking branch 'upstream/main' into auth-code-flow-2
TimoGlastra Nov 19, 2024
fd0c71f
chore: update
TimoGlastra Nov 19, 2024
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
5 changes: 5 additions & 0 deletions .changeset/grumpy-avocados-decide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@credo-ts/openid4vc': patch
---

fix(openid4vc): use `vp_formats` in client_metadata instead of `vp_formats supported` (#2089)
5 changes: 5 additions & 0 deletions .changeset/perfect-islands-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@credo-ts/openid4vc': patch
---

feat(openid4vc): support jwk thumbprint for openid token issuer
20 changes: 20 additions & 0 deletions .changeset/shiny-sheep-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@credo-ts/openid4vc': minor
---

feat(openid4vc): oid4vci authorization code flow, presentation during issuance and batch issuance.

This is a big change to OpenID4VCI in Credo, with the neccsary breaking changes since we first added it to the framework. Over time the spec has changed significantly, but also our understanding of the standards and protocols.

**Authorization Code Flow**
Credo now supports the authorization code flow, for both issuer and holder. An issuer can configure multiple authorization servers, and work with external authorization servers as well. The integration is based on OAuth2, with several extension specifications, mainly the OAuth2 JWT Access Token Profile, as well as Token Introspection (for opaque access tokens). Verification works out of the box, as longs as the authorization server has a `jwks_uri` configured. For Token Introspection it's also required to provide a `clientId` and `clientSecret` in the authorization server config.

To use an external authorization server, the authorization server MUST include the `issuer_state` parameter from the credential offer in the access token. Otherwise it's not possible for Credo to correlate the authorization session to the offer session.

The demo-openid contains an example with external authorization server, which can be used as reference. The Credo authorization server supports DPoP and PKCE.

**Batch Issuance**
The credential request to credential mapper has been updated to support multiple proofs, and also multiple credential instances. The client can now also handle batch issuance.

**Presentation During Issuance**
The presenation during issuance allows to request presentation using OID4VP before granting authorization for issuance of one or more credentials. This flow is automatically handled by the `resolveAuthorizationRequest` method on the oid4vci holder service.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ module.exports = {
'demo-openid/**',
'scripts/**',
'**/tests/**',
'tests/**',
],
env: {
jest: true,
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ jobs:
- run: mv coverage/coverage-final.json coverage/${{ matrix.shard }}.json
- uses: actions/upload-artifact@v4
with:
name: coverage-artifacts
name: coverage-artifacts-${{ matrix.node-version }}
path: coverage/${{ matrix.shard }}.json
overwrite: true

Expand Down Expand Up @@ -184,7 +184,7 @@ jobs:
- run: mv coverage/coverage-final.json coverage/e2e.json
- uses: actions/upload-artifact@v4
with:
name: coverage-artifacts
name: coverage-artifacts-${{ matrix.node-version }}
path: coverage/e2e.json
overwrite: true

Expand All @@ -195,9 +195,10 @@ jobs:
steps:
- uses: actions/download-artifact@v4
with:
name: coverage-artifacts
name: coverage-artifacts-20
path: coverage

- uses: codecov/codecov-action@v4
with:
directory: coverage
token: ${{ secrets.CODECOV_TOKEN }}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ aries-framework-*.tgz
coverage
.DS_Store
logs.txt
logs/
logs/

ngrok.auth.yml
46 changes: 41 additions & 5 deletions demo-openid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Alice, a former student of Faber College, connects with the College, is issued a

## Features

- ✅ Issuing a credential.
- ✅ Issuing a credential without authorization (pre-authorized code flow).
- ✅ Issuing a credenital with external authorization server (authorization code flow)
- ✅ Resolving a credential offer.
- ✅ Accepting a credential offer.
- ✅ Requesting a credential presentation.
Expand All @@ -29,7 +30,7 @@ Clone the Credo git repository:
git clone https://github.com/openwallet-foundation/credo-ts.git
```

Open three different terminals next to each other and in both, go to the demo folder:
Open four different terminals next to each other and in each, go to the demo folder:

```sh
cd credo-ts/demo-openid
Expand All @@ -41,13 +42,19 @@ Install the project in one of the terminals:
pnpm install
```

In the first terminal run the Issuer:
In the first terminal run the OpenID Provider:

```sh
pnpm provider
```

In the second terminal run the Issuer:

```sh
pnpm issuer
```

In the second terminal run the Holder:
In the third terminal run the Holder:

```sh
pnpm holder
Expand All @@ -65,7 +72,8 @@ To create a credential offer:

- Go to the Issuer terminal.
- Select `Create a credential offer`.
- Select `UniversityDegreeCredential`.
- Choose whether authorization is required
- Select the credential(s) you want to issue.
- Now copy the content INSIDE the quotes (without the quotes).

To resolve and accept the credential:
Expand All @@ -74,6 +82,8 @@ To resolve and accept the credential:
- Select `Resolve a credential offer`.
- Paste the content copied from the credential offer and hit enter.
- Select `Accept the credential offer`.
- Choose which credential(s) to accept
- If authorization is required a link will be printed in the terminal, open this in your browser. You can sign in using any username and password. Once authenticated return to the terminal
- You have now stored your credential.

To create a presentation request:
Expand All @@ -99,3 +109,29 @@ Exit:
Restart:

- Select 'restart', to shutdown the current program and start a new one

### Optional Proxy

By default all services will be started on `localhost`, and thus won't be reachable by other external services (such as a mobile wallet). If you want to expose the required services to the public, you need to expose multiple ngrok tunnels.

We can setup the tunnels automatically using ngrok. First make sure you have an ngrok account and get your access token from this page: https://dashboard.ngrok.com/get-started/setup/

Then copy the `ngrok.auth.example.yml` file to `ngrok.auth.yml`:

```sh
cp ngrok.auth.example.yml ngrok.auth.yml
```

And finally set the `authtoken` to the auth token as displayed in the ngrok dashboard.

Once set up, you can run the following command in a separate terminal window.

```sh
pnpm proxies
```

This will open three proxies. You should then run your demo environments with these proxies:

- `PROVIDER_HOST=https://d404-123-123-123-123.ngrok-free.app ISSUER_HOST=https://d738-123-123-123-123.ngrok-free.app pnpm provider` (ngrok url for port 3042)
- `PROVIDER_HOST=https://d404-123-123-123-123.ngrok-free.app ISSUER_HOST=https://d738-123-123-123-123.ngrok-free.app pnpm issuer` (ngrok url for port 2000)
- `VERIFIER_HOST=https://1d91-123-123-123-123.ngrok-free.app pnpm verifier` (ngrok url for port 4000)
2 changes: 2 additions & 0 deletions demo-openid/ngrok.auth.example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
authtoken: ea8af45e-0a76-44d5-b2a2-bab9d4bfb346
version: '2'
12 changes: 12 additions & 0 deletions demo-openid/ngrok.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: 2

tunnels:
issuer:
proto: http
addr: 2000
provider:
proto: http
addr: 3042
verifier:
proto: http
addr: 4000
13 changes: 10 additions & 3 deletions demo-openid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@
"license": "Apache-2.0",
"scripts": {
"issuer": "ts-node src/IssuerInquirer.ts",
"provider": "tsx src/provider.js",
"holder": "ts-node src/HolderInquirer.ts",
"verifier": "ts-node src/VerifierInquirer.ts"
"verifier": "ts-node src/VerifierInquirer.ts",
"proxies": "ngrok --config ngrok.yml,ngrok.auth.yml start provider issuer verifier"
},
"dependencies": {
"@hyperledger/anoncreds-nodejs": "^0.2.2",
"@hyperledger/aries-askar-nodejs": "^0.2.3",
"@hyperledger/indy-vdr-nodejs": "^0.2.2",
"@koa/bodyparser": "^5.1.1",
"express": "^4.18.1",
"inquirer": "^8.2.5"
"inquirer": "^8.2.5",
"jose": "^5.3.0",
"oidc-provider": "^8.4.6"
},
"devDependencies": {
"@credo-ts/askar": "workspace:*",
Expand All @@ -28,8 +33,10 @@
"@types/express": "^4.17.13",
"@types/figlet": "^1.5.4",
"@types/inquirer": "^8.2.6",
"@types/oidc-provider": "^8.4.4",
"clear": "^0.1.0",
"figlet": "^1.5.2",
"ts-node": "^10.9.2"
"ts-node": "^10.9.2",
"tsx": "^4.11.0"
}
}
12 changes: 11 additions & 1 deletion demo-openid/src/BaseAgent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import type { InitConfig, KeyDidCreateOptions, ModulesMap, VerificationMethod } from '@credo-ts/core'
import type { Express } from 'express'

import { Agent, DidKey, HttpOutboundTransport, KeyType, TypedArrayEncoder } from '@credo-ts/core'
import {
Agent,
ConsoleLogger,
DidKey,
HttpOutboundTransport,
KeyType,
LogLevel,
TypedArrayEncoder,
} from '@credo-ts/core'
import { HttpInboundTransport, agentDependencies } from '@credo-ts/node'
import express from 'express'

Expand All @@ -26,6 +34,8 @@ export class BaseAgent<AgentModules extends ModulesMap> {
const config = {
label: name,
walletConfig: { id: name, key: name },
allowInsecureHttpUrls: true,
logger: new ConsoleLogger(LogLevel.off),
} satisfies InitConfig

this.config = config
Expand Down
86 changes: 52 additions & 34 deletions demo-openid/src/BaseInquirer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,66 @@ export enum ConfirmOptions {
}

export class BaseInquirer {
public optionsInquirer: { type: string; prefix: string; name: string; message: string; choices: string[] }
public inputInquirer: { type: string; prefix: string; name: string; message: string; choices: string[] }

public constructor() {
this.optionsInquirer = {
type: 'list',
prefix: '',
name: 'options',
message: '',
choices: [],
}

this.inputInquirer = {
type: 'input',
prefix: '',
name: 'input',
message: '',
choices: [],
}
private optionsInquirer = {
type: 'list',
prefix: '',
name: 'options',
message: '',
choices: [],
}
private inputInquirer = {
type: 'input',
prefix: '',
name: 'input',
message: '',
choices: [],
}

public async pickOne(options: string[], title?: string): Promise<string> {
const result = await prompt([
{
...this.optionsInquirer,
message: title ?? Title.OptionsTitle,
choices: options,
},
])

public inquireOptions(promptOptions: string[]) {
this.optionsInquirer.message = Title.OptionsTitle
this.optionsInquirer.choices = promptOptions
return this.optionsInquirer
return result.options
}

public inquireInput(title: string) {
this.inputInquirer.message = title
return this.inputInquirer
public async pickMultiple(options: string[], title?: string): Promise<string[]> {
const result = await prompt([
{
...this.optionsInquirer,
message: title ?? Title.OptionsTitle,
choices: options,
type: 'checkbox',
},
])

return result.options
}

public inquireConfirmation(title: string) {
this.optionsInquirer.message = title
this.optionsInquirer.choices = [ConfirmOptions.Yes, ConfirmOptions.No]
return this.optionsInquirer
public async inquireInput(title: string): Promise<string> {
const result = await prompt([
{
...this.inputInquirer,
message: title,
},
])

return result.input
}

public async inquireMessage() {
this.inputInquirer.message = Title.MessageTitle
const message = await prompt([this.inputInquirer])
public async inquireConfirmation(title: string) {
const result = await prompt([
{
...this.optionsInquirer,
choices: [ConfirmOptions.Yes, ConfirmOptions.No],
message: title,
},
])

return message.input[0] === 'q' ? null : message.input
return result.options === ConfirmOptions.Yes
}
}
Loading
Loading