Socket
Book a DemoInstallSign in
Socket

@cef-ebsi/siop-auth

Package Overview
Dependencies
Maintainers
5
Versions
57
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cef-ebsi/siop-auth

EBSI DID Auth library

latest
npmnpm
Version
4.0.3
Version published
Maintainers
5
Created
Source

EBSI Logo

EBSI SIOP Auth Library

EBSI Auth library for natural persons and legal entities.

Table of Contents

  • Installation
  • Usage
  • License

Installation

npm install @cef-ebsi/siop-auth

or if you use yarn

yarn add @cef-ebsi/siop-auth

Usage

The current EBSI SIOP Auth implementation follows RFC - DID-OIDC for NP/LE Authentication to EBSI & Relying Party in EBSI V2, which uses two JSON Web Tokens (JWT), where the Agent uses a DID and it is validated in the DID Registry, and the Relying Party uses an App which is validated in the Trusted Apps Registry.

The current version supports ES256K, ES256, RS256, and EdDSA algorithms.

Note: This version does have support for custom claims. (i.e. using VerifiableID).

The creation of a Relying Party is as follows:

import { RP, Agent, verifyJwtTar, verifyJwtDid } from "@cef-ebsi/siop-auth";
import { generateKeyPair } from "jose";

const privateKeyRP = (await generateKeyPair("ES256K")).privateKey;
const rp = new RP({
  privateKey: privateKeyRP,
  alg: "ES256K",
  name: "test-appj2",
  kid: "https://api-test.ebsi.eu/trusted-apps-registry/v4/apps/test-appj2",
  redirectUri: "http://localhost:3000",
  didRegistry: "https://api-test.ebsi.eu/did-registry/v5/identifiers",
});

The creation of an Agent for Natural Person or Legal Entity is as follows:

const privateKeyAgent = (await generateKeyPair("ES256K")).privateKey;
const agent = new Agent({
  privateKey: privateKeyAgent,
  alg: "ES256K",
  kid: "did:ebsi:z21oU6xvBhsUQM49nw8KydE6#keys-1",
  siopV2: true,
});

The Authentication flow has the following steps involving a Natural Person or Legal Entity (NP/LE) and a relying party (RP):

  • The RP creates an authentication request
const uri = await rp.createRequest({
  claims: { ... },
  extraField: "extra data",
});

console.log(uri);

// openid://?response_type=id_token&client_id=http%253A%252F%252Flocalho
// st%253A3000&scope=openid%2520did_authn&nonce=33e7518b-b329-4824-809d-
// d1f548be850d&request=eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QiLCJraWQiOiJo
// dHRwczovL2FwaS50ZXN0LmludGVic2kueHl6L3RydXN0ZWQtYXBwcy1yZWdpc3RyeS92M
// i9hcHBzLzB4MDQ0ODNiOWJlMWUxODdhYWE2YmUwMzRkNjM0ZmRmMDgyNWRmYWYzNWI4Y2
// UyMjI5YTRjZDNmM2U4ZTg1ZjM0NSJ9.eyJzY29wZSI6Im9wZW5pZCBkaWRfYXV0aG4iLC
// JyZXNwb25zZV90eXBlIjoiaWRfdG9rZW4iLCJyZXNwb25zZV9tb2RlIjoicG9zdCIsImN
// saWVudF9pZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsInJlZGlyZWN0X3VyaSI6Imh0
// dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsIm5vbmNlIjoiMzNlNzUxOGItYjMyOS00ODI0LTgwO
// WQtZDFmNTQ4YmU4NTBkIiwiY2xhaW1zIjp7fSwiZXh0cmFGaWVsZCI6ImV4dHJhIGRhdG
// EiLCJpYXQiOjE2NDQ4MzY3MDEsImlzcyI6InRlc3QtYXBwajIiLCJleHAiOjE2NDQ4Mzc
// wMDF9.AZCS5WartvILNs5pBhIXPlmVi8ZI65obdBM36ZPLY5FxnQF6d7sRodsXqKbIAvX
// wTxCdh024bXeK6yNVzH4dfg
  • The agent verifies the authentication request
const urlParams = new URLSearchParams(uri.replace("openid://?", ""));
const { payload: payloadReq } = await verifyJwtTar(urlParams.get("request"), {
  trustedAppsRegistry: "https://api-test.ebsi.eu/trusted-apps-registry/v4/apps",
});
console.log(payloadReq);

// {
//   scope: 'openid did_authn',
//   response_type: 'id_token',
//   response_mode: 'post',
//   client_id: 'http://localhost:3000',
//   redirect_uri: 'http://localhost:3000',
//   nonce: '33e7518b-b329-4824-809d-d1f548be850d',
//   claims: {},
//   extraField: 'extra data',
//   iat: 1644836701,
//   iss: 'test-appj2',
//   exp: 1644837001
// }
  • The agent creates an authentication response
const encryptionKeyPair =
  alg === "EdDSA"
    ? crypto.generateKeyPairSync("x25519")
    : await generateKeyPair(alg);
const publicEncryptionKeyJwk = await exportJWK(encryptionKeyPair.publicKey);
const privateEncryptionKeyJwk = await exportJWK(encryptionKeyPair.privateKey);
const nonce = uuidv4();
const { urlEncoded } = await agent.createResponse({
  nonce,
  redirectUri: "http://localhost:3000",
  claims: {
    encryption_key: publicEncryptionKeyJwk,
  },
  extraField: "extra data",
});
console.log(urlEncoded);

// http://localhost:3000#id_token=eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QiLC
// JraWQiOiJkaWQ6ZWJzaTp6MjFvVTZ4dkJoc1VRTTQ5bnc4S3lkRTYja2V5cy0xIn0.eyJ
// zdWIiOiI2dTVlUW0zVVZnMTBoa0VOcGMzVVhxQ3lIbVp2WDhaVG16MHo0LW1lcnJvIiwi
// YXVkIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwic3ViX2p3ayI6eyJrdHkiOiJFQyIsI
// mNydiI6InNlY3AyNTZrMSIsIngiOiJJR292OXFSV2Q5M1E4S0ZaSWtsaUNSaGtwZmZTVW
// 1hejZjS2JJM0txc0lNIiwieSI6ImE3WFVGSFBCYjRSOVBXUkg1TXY5UWdVLXlHOW51YTF
// lbFJPTFVxMk5lZ2cifSwibm9uY2UiOiI5NWYyZjUxNi1jYjIwLTQwMzMtOTU1Yy1mNjc5
// MWU5Mzk0NjAiLCJjbGFpbXMiOnsiZW5jcnlwdGlvbl9rZXkiOnsia3R5IjoiRUMiLCJjc
// nYiOiJzZWNwMjU2azEiLCJ4IjoiSU1QbFdTSS14TkloQkFBNjRaZ3ZYZFlxWXl4U3dfYW
// wtMHZlWkFsbFVEQSIsInkiOiJHRThEcEdhRExobEs4MlJxQVVHZHVBOHhGS3RMUDQ2SUd
// TdWJnNndSVC1nIn19LCJleHRyYUZpZWxkIjoiZXh0cmEgZGF0YSIsImlhdCI6MTY0NDgz
// NjcwMiwiaXNzIjoiaHR0cHM6Ly9zZWxmLWlzc3VlZC5tZS92MiIsImV4cCI6MTY0NDgzN
// zAwMn0.9XIcv31iw0FqJ6AEmYMmAcf7s3kBTeA_S1vWf8231Qrg6bXILeezBzdHiIOexN
// EnuwtRvvtuS0EycS7B1Ke_mw
  • The RP verifies the authentication response It expects a callback to validate custom claims.
const idTokenAuthResponse = new URLSearchParams(
  urlEncoded.substring(urlEncoded.indexOf("#") + 1)
).get("id_token") as string;

const resVerification = await RP.verifyResponse(
  idTokenAuthResponse,
  async (claims) => {
    if (!claims || !claims.encryption_key)
      throw new Error("no encryption_key found in the claims");

  const { didDocument } = await verifyJwtDid(idTokenAuthResponse, {
    didRegistry: "https://api-test.ebsi.eu/did-registry/v5/identifiers",
  });

  const did = didDocument?.id ?? "";

    return { ...claims, did };
  }
);
console.log(resVerification);

// {
//   payload: {
//     did: 'did:ebsi:z21oU6xvBhsUQM49nw8KydE6',
//     sub: '6u5eQm3UVg10hkENpc3UXqCyHmZvX8ZTmz0z4-merro',
//     aud: 'http://localhost:3000',
//     sub_jwk: {
//       kty: 'EC',
//       crv: 'secp256k1',
//       x: 'IGov9qRWd93Q8KFZIkliCRhkpffSUmaz6cKbI3KqsIM',
//       y: 'a7XUFHPBb4R9PWRH5Mv9QgU-yG9nua1elROLUq2Negg'
//     },
//     nonce: '95f2f516-cb20-4033-955c-f6791e939460',
//     claims: { encryption_key: { ... } },
//     extraField: 'extra data',
//     iat: 1644836702,
//     iss: 'https://self-issued.me/v2',
//     exp: 1644837002
//   },
//   header: {
//     alg: 'ES256K',
//     typ: 'JWT',
//     kid: 'did:ebsi:z21oU6xvBhsUQM49nw8KydE6#keys-1'
//   },
//   resultClaims: {
//     encryption_key: {
//       kty: 'EC',
//       crv: 'secp256k1',
//       x: 'IMPlWSI-xNIhBAA64ZgvXdYqYyxSw_al-0veZAllUDA',
//       y: 'GE8DpGaDLhlK82RqAUGduA8xFKtLP46IGSubg6wRT-g'
//     },
//     did: 'did:ebsi:z21oU6xvBhsUQM49nw8KydE6'
//   }
// }
  • The RP creates an access token
const akeAccessToken = await rp.createAccessToken(resVerification);
console.log(akeAccessToken);

// {
//   ake1_enc_payload: '72da437d06839e054726e13df14fec940286ad1985026d50
//     e8fdd652402378c1986d1505f336765f1ae1be114b347225b298c4a4ab9c8031c
//     614e01ec96c4edcc9f0353b8bfee8a16220adf3f4a71e50f2b1a326c8ca1f6750
//     dcf4929ff19b0f1685e83533dd12d5c9db88636ba39175e2532e47a526edc612f
//     d7b56008a6a49f6bd52c96dcf38065a644e613772bb15037381a755d7663dace9
//     bb39abc2a3e8e4e0d59f86260d6043bc6171d97df581ca612239cd8007521b2d9
//     ccf3dc807189b25dfa4b645f5daa911c434e273ab2d82dd07efe44e16580248e5
//     360801ffbdc9e6e84795ea97c4c3cff3d187dc50c3e1d3f9b3d4b1ab5e0f01123
//     de96c1ef3a6fccd824ebdd5dd5d1a0238e25df3c1d3287cbd240071489d307edd
//     c4452e3e88a141b70e470e517af30fbb8aa0a75f74898e7af25cf51f7676de9b5
//     2cf2ee67033c27e6e94d9592d2a87a7c3b937b7f17b568060d4dbc42995a3da5d
//     2179876d12baf664e1b1d2bddb2c80f491a4a33949a3b3e2f1bb50a1a1b90b656
//     1470eace49149564b34ea9adad45c6f51dc6985a928546cea9b20679ebb797bc2
//     13b5e5773bc870c143cbbe82b34f5ba3479b45c53c0c96ed66da08b173b5fce82
//     9feed4d45d3187d37a87ef77a5f9d55bb226b8c8c31065bf8d7f66d4051ddc26c
//     6dd288859db9cd49258f9da9d72ca10f69050d554a30c4112d562db6773424237
//     24abfd608105931a75fedf483b2dd67d0316138f68e6b7f6b9ba0ea8ffc1e0ea3
//     b482810de56192b0d6af7adcc6905cc767a5f58d3c25e5c48b353bc14d2d0f964
//     a2b475bcfddcd6774d9d1c9cda7ab37b0f5056f031001e76c524606c0a9ed1f1c
//     01ec3d00c4fb3b7e6af6f72cb0eb7e85c3cb8ee1cd65b9fbcff6a975ecd6b4c6a
//     113cc8ec2ff58bfb3c0fbb7e68cdbea82398a040ddb2a89cb0da32561e5cb4837
//     618c33eaa753dd8e438eac938206e3a6e9020c98525aa5715580bbbc7169ecfd9
//     30269eae30be3c06c3fdfc24a7c64c9358c1239a073a880535dd788eba3c4b458
//     9b2db371d9ffd93ae7401ebe52ad71f8454ce36c7bbb2b346208db2c72ee3577e
//     70b0e1f8b70e40a0ca216220bb74d697f207bcca562d956380247e9fbfbdc68ad
//     90f5648d2cdeff24aa9889d3625979adef35a1941c34b01d5282cc3880dbc3105
//     f50bb4a80f4cccf7d155b02a618dfc87cf9f23dd15f4d03b509a16e356514922b
//     865858368a359aca260fe84969e3c6282c7c7442f0e3a690804e68d1ba9f9f73b
//     b61dd15864b72a7e27b4018',
//   ake1_sig_payload: {
//     ake1_nonce: '95f2f516-cb20-4033-955c-f6791e939460',
//     ake1_enc_payload: '72da437d06839e054726e13df14fec940286ad1985026d
//       50e8fdd652402378c1986d1505f336765f1ae1be114b347225b298c4a4ab9c8
//       031c614e01ec96c4edcc9f0353b8bfee8a16220adf3f4a71e50f2b1a326c8ca
//       1f6750dcf4929ff19b0f1685e83533dd12d5c9db88636ba39175e2532e47a52
//       6edc612fd7b56008a6a49f6bd52c96dcf38065a644e613772bb15037381a755
//       d7663dace9bb39abc2a3e8e4e0d59f86260d6043bc6171d97df581ca612239c
//       d8007521b2d9ccf3dc807189b25dfa4b645f5daa911c434e273ab2d82dd07ef
//       e44e16580248e5360801ffbdc9e6e84795ea97c4c3cff3d187dc50c3e1d3f9b
//       3d4b1ab5e0f01123de96c1ef3a6fccd824ebdd5dd5d1a0238e25df3c1d3287c
//       bd240071489d307eddc4452e3e88a141b70e470e517af30fbb8aa0a75f74898
//       e7af25cf51f7676de9b52cf2ee67033c27e6e94d9592d2a87a7c3b937b7f17b
//       568060d4dbc42995a3da5d2179876d12baf664e1b1d2bddb2c80f491a4a3394
//       9a3b3e2f1bb50a1a1b90b6561470eace49149564b34ea9adad45c6f51dc6985
//       a928546cea9b20679ebb797bc213b5e5773bc870c143cbbe82b34f5ba3479b4
//       5c53c0c96ed66da08b173b5fce829feed4d45d3187d37a87ef77a5f9d55bb22
//       6b8c8c31065bf8d7f66d4051ddc26c6dd288859db9cd49258f9da9d72ca10f6
//       9050d554a30c4112d562db677342423724abfd608105931a75fedf483b2dd67
//       d0316138f68e6b7f6b9ba0ea8ffc1e0ea3b482810de56192b0d6af7adcc6905
//       cc767a5f58d3c25e5c48b353bc14d2d0f964a2b475bcfddcd6774d9d1c9cda7
//       ab37b0f5056f031001e76c524606c0a9ed1f1c01ec3d00c4fb3b7e6af6f72cb
//       0eb7e85c3cb8ee1cd65b9fbcff6a975ecd6b4c6a113cc8ec2ff58bfb3c0fbb7
//       e68cdbea82398a040ddb2a89cb0da32561e5cb4837618c33eaa753dd8e438ea
//       c938206e3a6e9020c98525aa5715580bbbc7169ecfd930269eae30be3c06c3f
//       dfc24a7c64c9358c1239a073a880535dd788eba3c4b4589b2db371d9ffd93ae
//       7401ebe52ad71f8454ce36c7bbb2b346208db2c72ee3577e70b0e1f8b70e40a
//       0ca216220bb74d697f207bcca562d956380247e9fbfbdc68ad90f5648d2cdef
//       f24aa9889d3625979adef35a1941c34b01d5282cc3880dbc3105f50bb4a80f4
//       cccf7d155b02a618dfc87cf9f23dd15f4d03b509a16e356514922b865858368
//       a359aca260fe84969e3c6282c7c7442f0e3a690804e68d1ba9f9f73bb61dd15
//       864b72a7e27b4018',
//     did: 'did:ebsi:z21oU6xvBhsUQM49nw8KydE6',
//     iat: 1644836702,
//     iss: 'test-appj2',
//     exp: 1644837602
//   },
//   ake1_jws_detached: 'eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QiLCJraWQiOiJ
//     odHRwczovL2FwaS50ZXN0LmludGVic2kueHl6L3RydXN0ZWQtYXBwcy1yZWdpc3Ry
//     eS92Mi9hcHBzLzB4MDQ0ODNiOWJlMWUxODdhYWE2YmUwMzRkNjM0ZmRmMDgyNWRmY
//     WYzNWI4Y2UyMjI5YTRjZDNmM2U4ZTg1ZjM0NSJ9..FDIeLfGMw6rehGvvIMu7ybpf
//     g7BNDBDBHMMIfLm-9kfe9HNwFYh8jgjI7Z5bdl-u7-e9HPzfGMXuEgOHv15t3g',
//   kid: 'https://api-test.ebsi.eu/trusted-apps-registry/v4/apps/test-appj2'
// }
  • The agent verifies the ake response and gets the access token
const accessToken = await Agent.verifyAkeResponse(akeAccessToken, {
  nonce,
  privateEncryptionKeyJwk,
  trustedAppsRegistry: "https://api-test.ebsi.eu/trusted-apps-registry/v4/apps",
  alg: "ES256K",
});
console.log(accessToken);

// eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QiLCJraWQiOiJodHRwczovL2FwaS50ZXN0L
// mludGVic2kueHl6L3RydXN0ZWQtYXBwcy1yZWdpc3RyeS92Mi9hcHBzLzB4MDQ0ODNiOW
// JlMWUxODdhYWE2YmUwMzRkNjM0ZmRmMDgyNWRmYWYzNWI4Y2UyMjI5YTRjZDNmM2U4ZTg
// 1ZjM0NSJ9.eyJzdWIiOiJkaWQ6ZWJzaTp6MjFvVTZ4dkJoc1VRTTQ5bnc4S3lkRTYiLCJ
// kaWQiOiJkaWQ6ZWJzaTp6MjFvVTZ4dkJoc1VRTTQ5bnc4S3lkRTYiLCJhdWQiOiJlYnNp
// LWNvcmUtc2VydmljZXMiLCJub25jZSI6IjQ3M2M3N2VkLTZiOWItNDVjNi1hNThhLTgyM
// WI3NjA0NThmZiIsImxvZ2luX2hpbnQiOiJkaWRfc2lvcCIsImlhdCI6MTY0NDgzNjcwMi
// wiaXNzIjoidGVzdC1hcHBqMiIsImV4cCI6MTY0NDgzNzYwMn0.tlDsRB3w78DRdPhsL95
// mkOjB3x4Kmj7MHJisphOtUiM-v2_EoFLSACGBVPRd_YK9DWvNQ2bxR1BQbgRBgjdQtg
  • A different service verifies the access token
await verifyJwtTar(accessToken, {
  trustedAppsRegistry: "https://api-test.ebsi.eu/trusted-apps-registry/v4/apps",
  audience: "ebsi-core-services",
});

Prerequisites

It is assumed that the RP has an app registered in the trusted apps registry.

Agent for natural persons

The DID Method version 2 is also supported. Here is an example to create an agent for natural persons:

import { Agent } from "@cef-ebsi/siop-auth";
import { calculateJwkThumbprint, exportJWK, generateKeyPair, JWK } from "jose";

const keyPair = await generateKeyPair(alg);
const publicKeyJwkAgent = await exportJWK(keyPair.publicKey);
const thumbprint = await calculateJwkThumbprint(publicKeyJwkAgent, "sha256");
const subjectIdentifier = Buffer.from(thumbprint, "base64");
const kidAgent = `${EbsiWallet.createDid(
  "NATURAL_PERSON",
  subjectIdentifier,
)}#${thumbprint}`;
const agent = new Agent({
  privateKey: keyPair.privateKey,
  alg,
  kid: kidAgent,
  siopV2: true,
});

When creating the authentication response set the syntaxType to "did_subject" in the options in order to create the ID Token with the JWK in the headers. Example:

const { idToken } = await agent.createResponse(
  {
    nonce,
    redirectUri: callbackUrl,
    claims: {
      encryption_key: publicKeyEncryptionJwk,
    },
    responseMode: "form_post",
  },
  {
    syntaxType: "did_subject",
  },
);

License

Copyright (c) 2019 European Commission
Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work except in compliance with the Licence. You may obtain a copy of the Licence at:

Unless required by applicable law or agreed to in writing, software distributed under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Licence for the specific language governing permissions and limitations under the Licence.

FAQs

Package last updated on 16 Sep 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts