-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dynamic bridge endpoint #34
base: main
Are you sure you want to change the base?
Changes from all commits
b048558
76df29d
d1f13f9
aca8efe
768e9d5
f4d23d5
48de0dc
3a7a298
0a5deba
22d6cd1
2f4cc92
7971ea4
465ae3c
9325e78
016eed3
bc6ac56
e870782
aa0ac0f
7287aa6
a7d8579
aa12eb9
dd5eea3
a8b1d02
46e4a7f
1acabb3
db3d302
ad208dc
39307f5
5838a46
a874fdd
1eeb8ea
502eabf
8e38b37
4d6cddf
31afb38
e13df54
7b801d1
1cf1a09
a56d660
d753b23
f9fa302
4184e4e
64f7c7a
777d90d
600e244
da2deb4
069d8dd
4830bca
77cd474
3a34b2d
41a9c18
1dc3cdb
ee00bf5
5947bce
26f7282
65b9fc4
a8451bd
aa73910
4b08dcb
d8b544d
3b4ddd0
d00fbde
1a41415
adfa398
a41e499
38c6b2c
1eff760
0dcd032
0338cd8
842b6e3
6121998
9d1e32d
9566fac
2831eb9
f00f9b2
019d6af
35fbc11
cd5cadc
216e3d4
41d87d4
6c8fa50
3eabc09
846329c
f2115b6
946be1b
fcfa844
3d323a5
3b3bd20
fa9045e
8c575f1
68cb0b6
7d79109
5a71101
dffdcdc
c39c3e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,14 @@ You operate a service and want to allow your users to sign in using Verifiable | |
Credentials from a mobile wallet. But building that takes considerable time and | ||
expertise. | ||
|
||
<!-- prettier-ignore --> | ||
> [!NOTE] | ||
> As a new feature, the bridge now supports incremental authorization. | ||
> This allows the service provider to request additional Verifiable Credentials | ||
> from the user via the bridge. Please see the | ||
> [Incremental Authorization Flow](#incremental-authorization-flow) section for | ||
> more details. | ||
|
||
### The Solution | ||
|
||
A service provider can run this dockerized bridge software that acts as a normal | ||
|
@@ -147,6 +155,61 @@ sequenceDiagram | |
Client->>Browser: Provide access to protected service | ||
``` | ||
|
||
## Incremental Authorization Flow | ||
|
||
The user assumed to be logged in via the bridge and the service provider | ||
requests additional VC from user to perform incremental authorization. | ||
|
||
```mermaid | ||
sequenceDiagram | ||
autonumber | ||
actor User | ||
participant Wallet | ||
participant B as SSI-to-OIDC Bridge | ||
participant SP as Service Provider | ||
|
||
SP ->> B: "POST /api/dynamic/createTempAuthorization" | ||
B-->>SP: "Return UUID" | ||
|
||
SP->>B: "GET /api/dynamic/getQRCodeString" | ||
B-->>SP: "Return QR code string" | ||
|
||
SP->>User: "Send Auth. page containing QR code" | ||
|
||
SP->>B: "GET /api/dynamic/getAuthResponse" | ||
User->>Wallet: "Scan QR code" | ||
Wallet->>B: "GET /api/dynamic/presentCredentialById" | ||
B-->>Wallet: "Return metadata" | ||
Wallet-->>User: "Prompt user" | ||
|
||
User ->>Wallet: "Select VC(s)" | ||
Wallet->>B: "POST /api/dynamic/presentCredentialById" | ||
B-->>Wallet: "Success" | ||
|
||
B-->>SP: "Return Auth Response" | ||
``` | ||
|
||
### API Documentation | ||
|
||
This documentation provides all the necessary information to interact with the | ||
dynamic API endpoints. The API is documented using Swagger, which provides a | ||
user-friendly interface to explore and test the API. | ||
|
||
<!-- prettier-ignore --> | ||
> [!NOTE] | ||
> To access the Swagger documentation, you need to run the bridge in development mode and | ||
> navigate to `http://localhost:5002/api-docs`. | ||
|
||
To authenticate requests to the dynamic API in Swagger, you need to provide a | ||
valid API key. The API key is stored in the `.env` file in the `vclogin` folder. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the key is passed as an environment variable. Is the name documented somewhere further down? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, seeing it now. Would still slightly reword this. |
||
|
||
<!-- prettier-ignore --> | ||
> [!NOTE] | ||
> To authenticate requests to the dynamic API in Swagger, first click on | ||
> the "Authorize" button in the top right corner of the Swagger UI. Then, enter | ||
> the API key in the "Value" field with the format `API_KEY <api_key>`and click | ||
> on the "Authorize" button. | ||
|
||
## Running a Local Deployment | ||
|
||
A local deployment is a great way to test the bridge and to use it for | ||
|
@@ -175,10 +238,12 @@ proper domain has to be set up. | |
`/vclogin/.env` with key `PEX_DESCRIPTOR_OVERRIDE` if direct control over | ||
what wallets are asked for is desired (example for quick testing: | ||
`./__tests__/testdata/pex/descriptorEmailFromAltme.json`) | ||
6. at this point, it needs to be ensured that the container for the vclogin | ||
6. to be able to test dynamic endpoint APIs, you need to provide an API key in | ||
the `.env` file in the `vclogin` folder with the key `API_KEY`. | ||
7. at this point, it needs to be ensured that the container for the vclogin | ||
service is freshly built with the new env file: | ||
`docker compose down && docker compose build` | ||
7. `$ docker compose up` | ||
8. `$ docker compose up` | ||
|
||
To validate the running bridge with a simple OIDC client: | ||
|
||
|
@@ -246,6 +311,7 @@ PEX_DESCRIPTOR_OVERRIDE=./__tests__/testdata/pex/descriptorAnything.json | |
HYDRA_ADMIN_URL=http://localhost:5001 | ||
REDIS_HOST=localhost | ||
REDIS_PORT=6379 | ||
API_KEY=<api-key> | ||
``` | ||
|
||
_Note: The PEX_DESCRIPTOR_OVERRIDE is optional and provides a way to override | ||
|
@@ -261,7 +327,7 @@ refer to the end of the previous section. | |
|
||
## Running Tests | ||
|
||
This repository includes unit tests with `jest` and end-to-end tests with | ||
This repository includes unit tests with `vitest` and end-to-end tests with | ||
`playwright`. You may run them as follows: | ||
|
||
```bash | ||
|
@@ -278,7 +344,7 @@ credential, while forwarding all subject fields to the `id_token`: | |
```JSON | ||
[ | ||
{ | ||
"credentialID": "credential1", | ||
"credentialID": "1", | ||
"patterns": [ | ||
{ | ||
"issuer": "*", | ||
|
@@ -323,7 +389,7 @@ use of this could look like this: | |
```json | ||
[ | ||
{ | ||
"credentialId": "one", | ||
"credentialId": "1", | ||
"patterns": [ | ||
{ | ||
"issuer": "did:web:app.altme.io:issuer", | ||
|
@@ -384,6 +450,138 @@ logical operators that can combine multiple constraints: | |
- _or_ Takes two constraint objects `a` and `b`. | ||
- _not_ Takes one constraint object `a` | ||
|
||
### Multiple Policy Objects | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like the term "policy object" because this already has a name in the code and paper. Otherwise, very good addition. |
||
|
||
The bridge also supports multiple policy objects in a policy file. This allows | ||
for more complex scenarios where multiple credentials are needed to perform | ||
authorization. An example of such a policy file is: | ||
|
||
```json | ||
[ | ||
{ | ||
"credentialId": "1", | ||
"type": "EmailPass", | ||
"patterns": [ | ||
{ | ||
"issuer": "did:web:app.altme.io:issuer", | ||
"claims": [ | ||
{ | ||
"claimPath": "$.credentialSubject.email" | ||
} | ||
] | ||
} | ||
] | ||
}, | ||
{ | ||
"credentialId": "2", | ||
"type": "VerifiableId", | ||
"patterns": [ | ||
{ | ||
"issuer": "did:web:app.altme.io:issuer", | ||
"claims": [ | ||
{ | ||
"claimPath": "$.credentialSubject.id" | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] | ||
``` | ||
|
||
<!-- prettier-ignore --> | ||
> [!IMPORTANT] | ||
> Each `credentialId` should be unique across all policy objects, | ||
> and should have integer string values starting from 1, incrementing by 1 for each subsequent policy object. This helps us determine | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does that help us? I thought this worked before. And it allowed people to give more descriptive IDs. Also, now people could mess up configuration by not using numbers? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, if the credentialID is required to be a number, should it not be a JSON number type? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the first comment: The I guess I added this when I tested a scenario when we want to extract same claims from the different type of VCs. In practice, I think we can give unique string IDs again, but then we need to make sure that the constraints are evaluated correctly as well ( For the second comment: It's required to be an integer-valued string. "1" or "2" for example. |
||
> the correct policy object to apply to the VCs. | ||
|
||
<!-- prettier-ignore --> | ||
> [!IMPORTANT] | ||
> Altough the `type` field is an optional parameter, it needs to be | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know that the computational complexity of the previous algorithm was terrible, but I am not a fan of this type field. It leads to edge cases. What if you want to accept a student enrollment VC, but different Universities use different types? Now you cannot simply handle this in the patterns. This might even break that case completely. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You might be right; I think this way: Each organization would have a certain type of VC, and the service provider would accept that type of VC. The idea is similar to having registries so the verifier can know what credentials to accept. But then we need to tell the code to apply the correct policy to the correct type of VC. |
||
> present in a policy file that has multiple policy objects. This is crucial for the accurate application of policies. | ||
|
||
<!-- prettier-ignore --> | ||
> [!NOTE] | ||
> First we reorder the policy objects in a policy file based on the `credentialId` and | ||
> then we reorder the credentials in the VP based on the `type` field from the reordered policy file. | ||
> This ensures that each credential is matched with the correct policy object. | ||
|
||
The `type` field helps to determine which policy object should be applied to | ||
which type of credential. When multiple policy objects are used, this field | ||
becomes important because the order of VCs in the VP is not guaranteed. | ||
|
||
Users might submit VCs in a random order, so the type field ensures that each | ||
credential is matched with the correct policy regardless of the submission | ||
order. | ||
|
||
In the code snippet above | ||
|
||
- The first policy object is applied to the VC with type `EmailPass`. | ||
- The second policy object is applied to the VC with type `VerifiableId`. | ||
|
||
If the type fields are the same for multiple policy objects, the bridge will | ||
apply the policy objects to the VCs in the order they are defined in the policy | ||
file. | ||
|
||
### Multiple Constraints | ||
|
||
For each policy object, you can define constraints as defined in | ||
[Constraints](#constraints). An example of such a policy file is: | ||
|
||
```json | ||
[ | ||
{ | ||
"credentialId": "1", | ||
"type": "EmailPass", | ||
"patterns": [ | ||
{ | ||
"issuer": "did:web:app.altme.io:issuer", | ||
"claims": [ | ||
{ | ||
"claimPath": "$.credentialSubject.email" | ||
} | ||
], | ||
"constraint": { | ||
"op": "equalsDID", | ||
"a": "$VP.proof.verificationMethod", | ||
"b": "$1.credentialSubject.id" | ||
} | ||
} | ||
] | ||
}, | ||
{ | ||
"credentialId": "2", | ||
"type": "VerifiableId", | ||
"patterns": [ | ||
{ | ||
"issuer": "did:web:app.altme.io:issuer", | ||
"claims": [ | ||
{ | ||
"claimPath": "$.credentialSubject.id" | ||
} | ||
], | ||
"constraint": { | ||
"op": "equals", | ||
"a": "$2.credentialSubject.id", | ||
"b": "$1.credentialSubject.id" | ||
} | ||
} | ||
] | ||
} | ||
] | ||
``` | ||
|
||
You can cross reference different VCs using the constraints. As in the example | ||
below, the first VC's `credentialSubject.id` is compared with the second VC's | ||
`credentialSubject.id` in the second policy object. | ||
|
||
<!-- prettier-ignore --> | ||
> [!IMPORTANT] | ||
> You need to correctly define the JSONPaths of the constraint | ||
> operands to be able to perform constraints check. The JSONPaths should have a | ||
> structure like `$<credentialId>.<claimPath>` when having multiple policy | ||
> objects. | ||
|
||
## Token Introspection | ||
|
||
Look into the access token like this: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,31 @@ | ||
#!/bin/bash | ||
client=$(docker run --rm -it \ | ||
--network ory-hydra-net \ | ||
oryd/hydra:v2.2.0 \ | ||
create client --skip-tls-verify \ | ||
--name testclient \ | ||
--secret some-secret \ | ||
--grant-type authorization_code \ | ||
--response-type token,code,id_token \ | ||
--scope openid \ | ||
--redirect-uri http://localhost:9010/callback \ | ||
-e http://hydra:4445 \ | ||
--format json ) | ||
--network ory-hydra-net \ | ||
oryd/hydra:v2.2.0 \ | ||
create client --skip-tls-verify \ | ||
--name testclient \ | ||
--secret some-secret \ | ||
--grant-type authorization_code \ | ||
--response-type token,code,id_token \ | ||
--scope openid \ | ||
--redirect-uri "http://localhost:9010/callback" \ | ||
-e http://hydra:4445 \ | ||
--format json) | ||
|
||
echo $client | ||
echo "$client" | ||
|
||
client_id=$(echo $client | jq -r '.client_id') | ||
client_id=$(echo "$client" | jq -r ".client_id") | ||
|
||
docker run --rm -it \ | ||
--network ory-hydra-net \ | ||
-p 9010:9010 \ | ||
oryd/hydra:v2.2.0 \ | ||
perform authorization-code --skip-tls-verify \ | ||
--port 9010 \ | ||
--client-id $client_id \ | ||
--client-secret some-secret \ | ||
--redirect http://localhost:9010/callback \ | ||
--scope openid \ | ||
--auth-url http://localhost:5004/oauth2/auth \ | ||
--token-url http://hydra:4444/oauth2/token \ | ||
-e http://hydra:4444 | ||
--network ory-hydra-net \ | ||
-p 9010:9010 \ | ||
oryd/hydra:v2.2.0 \ | ||
perform authorization-code --skip-tls-verify \ | ||
--port 9010 \ | ||
--client-id "$client_id" \ | ||
--client-secret some-secret \ | ||
--redirect "http://localhost:9010/callback" \ | ||
--scope openid \ | ||
--auth-url http://localhost:5004/oauth2/auth \ | ||
--token-url http://hydra:4444/oauth2/token \ | ||
-e http://hydra:4444 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
LOGIN_POLICY=./__tests__/testdata/policies/acceptAnything.json | ||
DID_KEY_JWK={"kty":"OKP","crv":"Ed25519","x":"cwa3dufHNLg8aQb2eEUqTyoM1cKQW3XnOkMkj_AAl5M","d":"me03qhLByT-NKrfXDeji-lpADSpVOKWoaMUzv5EyzKY"} | ||
EXTERNAL_URL=http://example.com |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[ | ||
{ | ||
"id": "verifiableId", | ||
"name": "Input descriptor for login credential", | ||
"purpose": "Please provide your VerifiableId credential to sign-in.", | ||
"constraints": { | ||
"fields": [ | ||
{ | ||
"path": ["$.credentialSubject.type"], | ||
"filter": { | ||
"type": "string", | ||
"pattern": "VerifiableId" | ||
} | ||
} | ||
] | ||
} | ||
} | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
[ | ||
{ | ||
"credentialId": "credential1", | ||
"credentialId": "1", | ||
"patterns": [ | ||
{ | ||
"issuer": "*", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[ | ||
{ | ||
"credentialId": "some random string", | ||
"patterns": [ | ||
{ | ||
"issuer": "*", | ||
"claims": [ | ||
{ | ||
"claimPath": "$.credentialSubject.id" | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change this to just API? Or ensure consistent naming of dynamic authorization endpoints.