@affinidi/affinidi-did-auth-lib
TypeScript icon, indicating that this package has built-in type declarations

2.9.1 • Public • Published

Affinidi Did Auth Helpers

Usage:

This library allows proving that the client/user is the owner of provided DID. Based on this proof App builders can give the client/user access to the appropriate resources.

Did-Auth flow is similar to the Sharing VC flow, but without VC exchanges, since DID itself should be enough info from the client/user to authenticate. Flow diagram.

The high-level perspective of Did-Auth flow looks like requestresponsevalidation of response, where a response is a response(signed with the private key) to the challenge from the request.

For simplicity DID methods provided by SDK can be used for the implementation Did-Auth flow:

Implementation of DID Auth flow using AffinidiDidAuthService class

Implementation might be consists of two parts - the client side and the service side. The service side should provide some endpoints or trigger methods for the client to start the auth flow. Both clients and service can set a token timeout, during validation min(service token timeout, client token timeout) will be used.

Initiate AffinidiDidAuthService class (service side)

Did auth flow should be implemented e.g. on the service side on which the user wants to log in. Simplest way is to initialize AffinidiDidAuthService class with encryptedSeed and encryptionKey options. For the client-side could be used the same approach, but with the client encryptedSeed and encryptionKey.

/**
 * sericeEncryptedSeed {String} (required) - encrypted seed, previously generated for service
 * serviceEncryptionKey {String} (required) - encreption key, previously generated for service
  */
const affinidiDidAuthService = new AffinidiDidAuthService({
  encryptedSeed: sericeEncryptedSeed,
  encryptionKey: serviceEncryptionKey,
})

Also AffinidiDidAuthService could be created using did, keyId, keyVault as options. This approach is for batter backward compatibility. If needed more low-level implementation could be created with the DidAuthServerService class for the service side and the DidAuthClientService class for the client side.

import { KeysService, KeyVault, DidDocumentService, LocalKeyVault } from '@affinidi/common'

const keyService = new KeysService(sericeEncryptedSeed, serviceEncryptionKey)
const documentService = DidDocumentService.createDidDocumentService(keyService)
/**
 * did {String} - anchored did of the local entity
 * keyId {String} - key id used for signing, defaults to `<did>#primary`
 * keyVault {KeyVault} - key store backed implementation when the seed is known, holding signing keys
 */
const affinidiDidAuthService = new AffinidiDidAuthService({
  did: documentService.getMyDid(),
  keyId: documentService.getKeyId(),
  keyVault: new LocalKeyVault(keyService),
})

Creation of the request token(service side)

Client DID(ownership of which client is proved) might be provided. Use createDidAuthRequestToken method to create Did-Auth request token(expires in 1 minute by default). That request token is a JWT signed with the service's public key. It should be sent back to the client for further steps.

/**
 * audienceDid {String} - audience DID of generated JWT request token (the one's DID to whom the token was sent)
 * expiresAt {Number} (optional) - expires (at specified time) of generated JWT request token (1 minute by default)
 */
const authDidRequestToken = await affinidiDidAuthService.createDidAuthRequestToken(audienceDid, expiresAt)

Building of the response token(client side)

To create a response token it is recommended to initialize the AffinidiDidAuthService class with encryptedSeed and encryptionKey options on the client side in the same way as for the service side. Use createDidAuthResponseToken method to create Did-Auth response token(expires in 12 hours by default). It is necessary to have a fresh request token from the service.

/**
 * authDidRequestToken {String} - signed JWT request token from the service
 * options {Object} (optional) - key value object with additional options
 * options.maxTokenValidInMs {Number} (optional) - maximum token validity period in milliseconds(12 hours by default)
 */
const responseToken = await affinidiDidAuthService.createDidAuthResponseToken(authDidRequestToken, options)

A Response token (JWT) should be used to authenticate the user at the service side.

As a best practice, it is recommended to use the LocalExpiringDidAuthResponseToken utility class that can encapsulate a responseToken. And keep track of its expiry time according to the local time of the client. This is needed in case the local time of the client can diverge from the service time.

/**
 * tokenRequestTime {Number} - token request time, recommended to use Date.now(), could be created before calling `createDidAuthRequestToken` method
 * responseToken {String} - response token (JWT) built on the client side
 */
const auth = LocalExpiringDidAuthResponseToken.initialize(tokenRequestTime, responseToken)
// Check if token is expired.
auth.isExpiredAt(Date.now())

Validating of the response token(service side)

It is recommended to implement the Authorization header for every endpoint and expect to receive the response token from the client side. Each time given response token might be validated by the service using the verifyDidAuthResponseToken method

/**
 * responseToken {String} - response token (JWT) built on the client side.
 * environment {Stirng} (required) - environment, could be `dev`, `staging` or `prod`
 * accessApiKey {Stirng} (required) - client access api key(uuid) 
 */
const isValid = await affinidiDidAuthService.verifyDidAuthResponseToken(responseToken, {
  environment: environment,
  accessApiKey: accessApiKey,
})

Generate accessApiKey. If the response token is not valid service should throw an error, otherwise, the request should proceed.

Implementation of the service side part of DID Auth flow using DidAuthServerService class

DidAuthServerService is introduced for implementation of the DID-Auth flow on the service side. The benefit of using DidAuthServerService is - it provides only two methods that might be in use for the service:

  • to create a request token to challenge the client App (createDidAuthRequestToken)
  • to validate the response token during the authentication process (verifyDidAuthResponseToken).

This class could be useful if you would like to implement your own did-auth service as an additional layer at api-gateway.

Example:

import { DidAuthServerService, Signer } from '@affinidi/affinidi-did-auth-lib'
import { KeysService, DidDocumentService, LocalKeyVault } from '@affinidi/common'
import { parse } from 'did-resolver'

// `serviceEncryptedSeed` and `serviceEncryptionKey` should be taken from the wallet.
const keyService = new KeysService(serviceEncryptedSeed, serviceEncryptionKey)
const documentService = DidDocumentService.createDidDocumentService(keyService)
// Initialize `Signer` class instance.
const signer = new Signer({
  did: documentService.getMyDid(),
  keyId: documentService.getKeyId(),
  keyVault: new LocalKeyVault(keyService),
})
// Parsing the service DID.
const verifierDid = parse(documentService.getMyDid()).did
// Initialize `DidAuthServerService` class.
const serverService = new DidAuthServerService(verifierDid, signer, null)
// Parsing the client DID (might come inside the client request as e.g. `audienceLongDid`).
const audienceDid = parse(audienceLongDid).did
const authDidRequestToken = await serverService.createDidAuthRequestToken(audienceDid, expiresAt)

Using DidAuthClientService class to implement the client side part of DID Auth flow

DidAuthClientService is introduced for implementation of DID-Auth flow on the client side. This class provides only createDidAuthResponseToken(create response token) which might be used only with LocalExpiringDidAuthResponseToken to prevent time inconsistent issues between service and client. It could be used as an alternative to the AffinidiDidAuthService class.

Example:

import { LocalExpiringDidAuthResponseToken, DidAuthClientService, Signer } from '@affinidi/affinidi-did-auth-lib'
import { KeysService, DidDocumentService, LocalKeyVault } from '@affinidi/common'

// `clientEncryptedSeed` and `clientEncryptionKey` should be taken from the wallet.
const keyService = new KeysService(clientEncryptedSeed, clientEncryptionKey)
const documentService = DidDocumentService.createDidDocumentService(keyService)
// Initialize `Signer` class instance.
const signer = new Signer({
  did: documentService.getMyDid(),
  keyId: documentService.getKeyId(),
  keyVault: new LocalKeyVault(keyService),
})
// Initialize `DidAuthClientService` class instance.
const didAuthClientService = new DidAuthClientService(signer)

let auth: LocalExpiringDidAuthResponseToken
// Example of the method that could be implemented to prepare a response token
// to authenticate clients on the service side using the `Did-Auth` flow.
const getAuth = async (maxTokenValidInMs: number): Promise<LocalExpiringDidAuthResponseToken> => {
  // Check if the token exists and if the token is expired.
  if (auth && !auth.isExpiredAt(Date.now())) {
    return auth
  }
  // Setting token request time.
  const tokenRequestTime = Date.now()
  // Example of method that pulls request token from the service side.
  const requestToken = await pullDidAuthRequestToken({})
  const responseToken = await didAuthClientService.createDidAuthResponseToken(requestToken, { maxTokenValidInMs })
  
  auth = LocalExpiringDidAuthResponseToken.initialize(tokenRequestTime, responseToken)
  
  return auth
}

Testing

For unit tests we are using Mocha and Chai.

npm run test

Readme

Keywords

Package Sidebar

Install

npm i @affinidi/affinidi-did-auth-lib

Weekly Downloads

11

Version

2.9.1

License

ISC

Unpacked Size

66.1 kB

Total Files

39

Last publish

Collaborators

  • rohitjjw
  • maratsh-affinidi
  • robert-affinidi
  • standemchuk