Swiss E-ID Beta, a First Look

10min read
 -
Gian-Luca Frei
 -
November 12, 2025

This post initiates a technical examination of the public beta for the Swiss E-ID (electronic identity). We implement a minimal PoC for local verification. This post is the first in a series:

Part 1: Trying out the Swiss E-ID Beta

To try out the Swiss E-ID do the following:

  • Install the swiyu wallet application and request a beta E-ID credential via the official portal: https://www.eid.admin.ch/en/public-beta-e.
  • Validate the issued credential against the Beta Credential Service (BCS) verification endpoint: https://www.bcs.admin.ch/bcs-web/#/beta-id/verification.
Beta Credential Service showing a QR code to scan with the Swiyu app.

Deconstructing the Request Object

Inspecting the QR code generated by the BCS reveals a link that points to a Request Object:

https://bcs.admin.ch/bcs-web/verifier-agent/oid4vp/api/request-object/7c7c33e5-e51c-4e63-b2fd-2b1f06cdffb6


This Request Object is a JSON Web Token (JWT) of the type `oauth-authz-req+jwt`, compliant with the OpenID for Verifiable Presentations (OID4VP) specification. The Request Object's payload outlines the required verifiable credential data using a presentation_definition. Crucially, it includes a filter to mandate the correct Verifiable Credential Type (`vct`):

{
    "path": [
        "$.vct"
    ],
    "filter": {
        "type": "string",
        "const": "betaid-sdjwt"
    }
}

The betaid-sdjwt value explicitly identifies the verifiable credential issued by the Beta Credential Service (BCS), distinguishing it from other potential credentials (e.g., driving licenses) the Swiss E-ID wallet may hold.

BCS Request Object (Decoded)

The complete decoded JWT body demonstrates the required format (`vc+sd-jwt`) and the requested credential claims:

{
  "kid": "did:tdw:QmPEZPhDFR4nEYSFK5bMnvECqdpf1tPTPJuWs9QrMjCumw:identifier-reg.trust-infra.swiyu-int.admin.ch:api:v1:did:9a5559f0-b81c-4368-a170-e7b4ae424527#assert-key-b720c10d-34be-4e2e-9d87-cecdf8e6a00f",
  "typ": "oauth-authz-req+jwt",
  "alg": "ES256"
}.{
  "response_uri": "https://bcs.admin.ch/bcs-web/verifier-agent/oid4vp/api/request-object/7c7c33e5-e51c-4e63-b2fd-2b1f06cdffb6/response-data",
  "client_id_scheme": "did",
  "iss": "did:tdw:QmPEZPhDFR4nEYSFK5bMnvECqdpf1tPTPJuWs9QrMjCumw:identifier-reg.trust-infra.swiyu-int.admin.ch:api:v1:did:9a5559f0-b81c-4368-a170-e7b4ae424527",
  "response_type": "vp_token",
  "presentation_definition": {
    "id": "myRequestId",
    "name": "myName",
    "purpose": "myPurpose",
    "format": {},
    "input_descriptors": [
      {
        "id": "myInputDescriptorID",
        "name": "BCS",
        "format": {
          "vc+sd-jwt": {
            "sd-jwt_alg_values": [
              "ES256"
            ],
            "kb-jwt_alg_values": [
              "ES256"
            ]
          }
        },
        "constraints": {
          "format": {},
          "fields": [
// ... (omitted claims for brevity)
            {
              "path": [
                "$.vct"
              ],
              "filter": {
                "type": "string",
                "const": "betaid-sdjwt"
              }
            }
          ]
        }
      }
    ]
  },
  "nonce": "Ac5c/JojM/t43YCeNPyghgPe4HCGU+fG",
  "version": "1.0",
  "client_id": "did:tdw:QmPEZPhDFR4nEYSFK5bMnvECqdpf1tPTPJuWs9QrMjCumw:identifier-reg.trust-infra.swiyu-int.admin.ch:api:v1:did:9a5559f0-b81c-4368-a170-e7b4ae424527",
  "client_metadata": {
// ... (omitted client_metadata for brevity)
    "vp_formats": {
      "jwt_vp": {
        "alg": [
          "ES256"
        ]
      }
    },
    "client_name": "swiyu Beta Credential Service (BCS)"
  },
  "response_mode": "direct_post"
}.[Signature]

Part 2: Implementing a Local Verifier Service PoC

To validate credentials, we deploy the SWIYU Verifier Service locally. This requires a Decentralized Identifier (DID) configured in the SWIYU ecosystem.

DID Registration

curl https://raw.githubusercontent.com/swiyu-admin-ch/swiyu-verifier/refs/heads/main/sample.compose.yml > sample.compose.yml
curl https://raw.githubusercontent.com/swiyu-admin-ch/swiyu-verifier/refs/heads/main/.env > .env

The Verifier Service must be registered in the SWIYU Trust Infrastructure. This involves several prerequisite steps using the SWIYU platform and command-line tools.

Prerequisite Access:

  1. Create an AGOV Login: https://www.me.agov.admin.ch/registration
  2. Register as a Business Partner: https://portal.trust-infra.swiyu-int.admin.ch/ui/organizations
  3. Obtain a `swiyucorebusiness_trust` access token: https://selfservice.api.admin.ch/api-selfservice/apis

All done you can set the following local environement variables:

export SWIYU_IDENTIFIER_REGISTRY_ACCESS_TOKEN="<Your-Access-Token>"
export SWIYU_PARTNER_ID="<Your-Partner-ID>"
export SWIYU_IDENTIFIER_REGISTRY_URL="https://identifier-reg-api.trust-infra.swiyu-int.admin.ch"

Create DID Space: Register a new identifier entry for the DID log:

curl \
  -H "Authorization: Bearer $SWIYU_IDENTIFIER_REGISTRY_ACCESS_TOKEN" \
  -X POST "$SWIYU_IDENTIFIER_REGISTRY_URL/api/v1/identifier/business-entities/$SWIYU_PARTNER_ID/identifier-entries"

Response:

{"id":"0a0e0186-1f95-4c4f-a63d-768d183bc499","identifierRegistryUrl":"https://identifier-reg.trust-infra.swiyu-int.admin.ch/api/v1/did/0a0e0186-1f95-4c4f-a63d-768d183bc499"}

Store the `id` from the response as `$IDENTIFIER_REGISTRY_ID`.

Generate and publish DID Keys

Generate DID Log: Use the didtoolbox utility to generate the keys and the DID log file (didlog.jsonl).

curl https://repo1.maven.org/maven2/io/github/swiyu-admin-ch/didtoolbox/1.3.1/didtoolbox-1.3.1-jar-with-dependencies.jar > didtoolbox.jar
java -jar didtoolbox.jar create --identifier-registry-url $IDENTIFIER_REGISTRY_URL

Upload the generated log to the public registry:

curl --data-binary @didlog.jsonl \
  -H "Authorization: Bearer $SWIYU_IDENTIFIER_REGISTRY_ACCESS_TOKEN" \
  -H "Content-Type: application/jsonl+json" \
  -X PUT "https://identifier-reg-api.trust-infra.swiyu-int.admin.ch/api/v1/identifier/business-entities/$SWIYU_PARTNER_ID/identifier-entries/$IDENTIFIER_REGISTRY_ID"

Expected response: `200 OK`

Running the Verifier Service

Now we are ready to validate the beta id. We use the provided Docker Compose files and expose the local service to the internet using ngrok to enable the mobile wallet to reach it.

curl https://raw.githubusercontent.com/swiyu-admin-ch/swiyu-verifier/refs/heads/main/sample.compose.yml > sample.compose.yml
curl https://raw.githubusercontent.com/swiyu-admin-ch/swiyu-verifier/refs/heads/main/.env > .env

There is a swagger ui at /swagger-ui.html

You need to set the .env variables with the generates DID values.

ngrok http 8083

Start the service:

docker compose -f sample.compose.yml up

### Requesting a Credential

The local verifier is instructed to request a presentation for only the given_name and family_name, filtered for the betaid-sdjwt credential. For that we store the request to the servifier service in a local file: `vs_request.json`

{
    "accepted_issuer_dids": [],
    "jwt_secured_authorization_request": true,
    "presentation_definition": {
        "id": "myRequestId",
        "name": "Test Verification",
        "purpose": "We want to test a new Verifier",
        "input_descriptors": [
            {
                "id": "myInputDescriptorID",
                "name": "BCS",
                "format": {
                    "vc+sd-jwt": {
                        "sd-jwt_alg_values": [
                            "ES26"
                        ],
                        "kb-jwt_alg_values": [
                            "ES256"
                        ]
                    }
                },
                "constraints": {
                    "fields": [
                        { "path": [ "$.family_name" ] },
                        { "path": [ "$.given_name" ] },
                        {
                            "path": [ "$.vct" ],
                            "filter": {
                                "type": "string",
                                "const": "betaid-sdjwt"
                            }
                        }
                    ]
                }
            }
        ]
    }
}

Send the request to local verifier service:

curl -X POST http://localhost:8083/management/api/verifications -H "Content-Type: application/json" --data-binary @vs_request.json > vs_request_response.json

The response contains the necessary OID4VP Request Object URL exposed via ngrok.

{
// ...
"verification_url": "https://bf83b10ab928.ngrok-free.app/oid4vp/api/request-object/e267ddcd-145f-487e-9b69-e9ac2e102b2e",
"verification_deeplink": "swiyu-verify://?client_id=..."
// ...
}

Now we generate a QR code of the request url and scan it with our SWIYU app

qrencode -t ANSIUTF8 $(jq -r '.verification_url' vs_request_response.json)

The QR code is scanned by the swiyu app, which returns a Verifiable Presentation (VP) to the verifier's response_uri.

Poll for Result

To get the result of the verification there we can check the status of the request with the verifier service. It contains the presented values in the response.

curl "http://localhost:8083/management/api/verifications/$(jq -r '.id' vs_request_response.json)" > vs_result.json
jq -r ".wallet_response.credential_subject_data.given_name" vs_result.json

The final vs_result.json confirms "state": "SUCCESS" and reveals the selectively disclosed claims:

vs_result.json

{
// ...
    "state": "SUCCESS",
    "wallet_response": {
        "credential_subject_data": {
            "family_name": "National",
            "given_name": "Helvetia",
            "vct": "betaid-sdjwt",
            "iss": "did:tdw:QmPEZPhDFR4nEYSFK5bMnvECqdpf1tPTPJuWs9QrMjCumw:identifier-reg.trust-infra.swiyu-int.admin.ch:api:v1:did:9a5559f0-b81c-4368-a170-e7b4ae424527",
// ...
        }
    }
// ...
}

Wallet-Verifier Communication Analysis

The network traffic logged by ngrok (http://127.0.0.1:4040) confirms the OID4VP exchange:

  1. `GET  /oid4vp/api/request-object/{ID}`: The wallet fetches the Request Object.
  2. `POST /oid4vp/api/request-object/{ID}/response-data`: The wallet sends the Verifiable Presentation, as an `x-www-form-urlencoded` body containing a `vp_token`.

The vp_token structure is a VC+SD-JWT (Verifiable Credential plus Selective Disclosure JWT), a core part of the OID4VP specification.

presentation_submission=...&vp_token=eyJ2ZXIiO...

Looking into it we can see that it is a `vptoken`. We can read more about it in the OID4VP specification: https://openid.net/specs/openid-4-verifiable-presentations-1_0.html

(Security) Notes

The use of an empty accepted_issuer_dids array ([]) in the verification request seems problematic. While one might assume this rejects all issuers, the observed behavior is the opposite: it appears to skip issuer validation entirely.

This design choice means the Verifier Service defaults to accepting any Verifiable Credential (VC) as long as the presentation is valid and the vct filter (betaid-sdjwt) is met. It permits verification to succeed with any self-issued VC that merely asserts the correct type, regardless of the issuing DID. We will try to exploit this weakness in the next post where we try to issue a credential.

For production hardening, endpoint exposure must be locked down: only the public-facing /oid4vp/** endpoints should be exposed, while all management APIs like /management/** should be firewalled.

For the front-end, always use the verification_deeplink URI (swiyu-verify://?client_id=...&request_uri=...) instead of the raw verification_url. The deeplink is the correct mechanism to instruct the mobile OS to immediately launch the wallet app upon scanning, which is the expected user flow.

Gian-Luca Frei

Gian-Luca Frei is security engineer and specialist for login and authentication. He has a proven track record of securing systems with the highest security standards, including e-banking portals and health applications.

He previously spent 6 years at Zühlke as a security consultant, working in a highly international setting across Switzerland, Singapore, and Hong Kong.

Gian-Luca is also the founder and co-leader of the OWASP Application Gateway Project.

He has a keen interest in modern cryptographic protocols, and his contributions were recognized with the ISSS Excellence Award in 2019.

Our latest article

AI for Development
At IdentityPlane, we don't treat AI as a futuristic replacement for developers; we use it as a powerful co-pilot to write high-quality, cloud-native security software faster. Our focus is simple: leverage AI to improve our code quality and enjoy the speed benefits that come with it.
November 21, 2025
5min
No items found.

Offer Your User Secure and Easy Login Experinces?

Ready to elevate your very first user touchpoin? Contact us today and transform your business with better user experiences.

AirTide Webflow template Image