How to Gain Server-Side Trust on the Client-Side? DPoP (RFC 9449)

Verifying client identity is crucial in data transmission, commonly achieved through mutual Transport Layer Security (mTLS i.e. RFC 8705), where both client and server authenticate using digital certificates. However, obtaining client TLS certificates can be complex.

The DPoP (Demonstration of Proof of Possession – RFC 9449) specification, uses cryptographic key pairs instead of TLS certificates, simplifying the process. It securely links a token to a specific HTTP request and client by including a DPoP proof (typically a JWT with a cryptographic signature) with each access token request. This method enhances security against token theft and allows dynamic key generation, operating at the application level.

RFC 9449 vs RFC 8705

RFC 9449 (DPoP) simplifies authentication by letting clients generate a private and public key pair, eliminating the need for a TLS certificate. In contrast, RFC 8705 (mTLS) requires TLS certificates for bidirectional authentication, posing challenges in certificate distribution to clients.

Challenges with DPoP:

  • Securely storing and distributing public keys: Ensuring clients can register and update their keys securely without risk of unauthorized access or manipulation.
FeatureDPoP (RFC 9449)mTLS (RFC 8705)Token Binding (RFC 8471)
AuthenticationClient-side signature using key pairsClient-side TLS certificateClient-side cryptographic token
Integration LayerApplication layerTransport layer (HTTPS)Application layer
Complexity levelSimpler key managementRequires TLS certificate infrastructureBrowser support is limited
ProtectionToken theft, replay, unauthorized usageMan-in-the-middle attacks, eavesdroppingToken theft, replay
PerformanceLess overhead than mTLSSome overhead due to TLS handshakeNegligible overhead
FlexibilityCan be used with various authentication flowsTied to TLS-based connectionsCan be used with different protocols

1. Client: Generate Key pair using Web Crypto API (https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API)

  • Private keys can be non-extractable for more security.
  • For demonstration purposes, the encryption algorithm is fixed, and the JWK format is not utilized. 
  • You can execute code snippets in the browser console to view them in action.
const payload = { "key": "abcd" };
async function generateKeyPair() {
    const keyPair = await window.crypto.subtle.generateKey(
        {
            name: "RSASSA-PKCS1-v1_5",
            modulusLength: 2048,
            publicExponent: new Uint8Array([1, 0, 1]),
            hash: { name: "SHA-256" },
        },
        true,
        ["sign", "verify"]
    );
    return keyPair;
}
const keys =  await generateKeyPair()
console.log(keys);

2. Client: Sign a request using the Private key

//gen signature
async function signData(privateKey, data) {
    const encoder = new TextEncoder();
    const encodedData = encoder.encode(data);
    const signature = await window.crypto.subtle.sign(
        "RSASSA-PKCS1-v1_5",
        privateKey,
        encodedData
    );

    return signature;
}
const signature = await signData(keys.privateKey, payload);
console.log(signature);

3. Server: Signature Verification on the server side

//verify signature
const receivedPayload = { "key": "abcd" };
async function verifySignature(publicKey, signature, data) {
  const encoder = new TextEncoder();
  const encodedData = encoder.encode(data);

  const isValid = await window.crypto.subtle.verify(
    {
      name: "RSASSA-PKCS1-v1_5",
      hash: { name: "SHA-256" },
    },
    publicKey,
    signature,
    encodedData
  );

  return isValid;
}
const isValid = await verifySignature(keys.publicKey, sigV4, receivedPayload);
console.log(isValid);

4. Server: process data and send back requested data if the signature is valid

ZooKeeper JavaScript Implementation

ZooKeeper JavaScript Implementation

Def from Apache: ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.

ZooKeeper Node Class Structure:

class ZooNode {
  key = null;
  value = null;
  watchers = [];
  children = [];
  constructor(key, value, watcherFn) {
    this.key = key;
    this.value = value;
    if (watcherFn) {
      this.watchers.push(watcherFn);
    }
  }
}

ZooKeeper Implementation:


class ZooKeeper {
  root = new ZooNode('root');

  /*get path segments*/
  getPathSegments(path) {
    const ps = path.split('/');
    ps.shift();
    return ps;
  }

  /*call watchers*/
  callWatchers(fns) {
    fns.forEach(fn => fn?.());
  }

  /*find a node and it's watchers*/
  dfs(root, pathSegments, idx, watcherFns = []) {
    if (!pathSegments.length) {
      return { node: root, watcherFns: [...root.watchers] };
    }
    const key = pathSegments[idx];
    watcherFns = watcherFns.concat(root.watchers);
    const nodeIdx = root.children.map(p => p.key).indexOf(key);
    if (nodeIdx > -1) {
      const nextRoot = root.children[nodeIdx];
      if (pathSegments.length === idx + 1) {
        watcherFns = watcherFns.concat(nextRoot.watchers);
        return { node: nextRoot, watcherFns };
      }
      return this.dfs(nextRoot, pathSegments, idx + 1, watcherFns);
    }
  }

  create(path, value) {
    const ps = this.getPathSegments(path, true);
    const newChildKey = ps.pop();
    const { node: parentNode, watcherFns } = this.dfs(this.root, ps, 0) || {};
    if (parentNode) {
      parentNode.children.push(new ZooNode(newChildKey, value));
      this.callWatchers(watcherFns);
    } else {
      console.log(`unable to add, path is not exist: ${path}`);
    }
  }

  setValue(path, value) {
    const ps = this.getPathSegments(path, true);
    const { node, watcherFns } = this.dfs(this.root, ps, 0) || {};
    if (node) {
      node.value = value;
      this.callWatchers(watcherFns);
    } else {
      console.log(`unable to update, path is not exist: ${path}`);
    }
  }

  getValue(path) {
    const ps = this.getPathSegments(path, true);
    const { node } = this.dfs(this.root, ps, 0) || {};
    if (node) {
      return node.value;
    } else {
      console.log(`unable to update, path is not exist: ${path}`);
    }
  }

  watch(path, fn) {
    const ps = this.getPathSegments(path, true);
    const { node } = this.dfs(this.root, ps, 0) || {};
    if (node) {
      node.watchers = [fn];//concat for multiple listeners
    } else {
      console.log(`unable to update, path is not exist: ${path}`);
    }
  }
}

Execution:

let z = new ZooKeeper();
z.create('/a', 'foo');
z.create('/a/c', 'bar');
z.create('/a/d', 'foo_d');
z.setValue('/a/d', 'foo_d_update');
console.log(z.getValue('/a/d')); 
//foo_d_update

z.watch('/a/d', () => { console.log('a watch at /a/d') });
z.watch('/a', () => { console.log('a watch at /a') });
z.setValue('/a/d', 'foo_d_update_again'); 
//1. a watch at /a 
//2. a watch at /a/d

z.setValue('/a', 'foo_a_update_again');
//a watch at /a

Template Driven Forms API – Angular v5.x

Angular has rich form support regarding both template driven and reactive forms. This article is going to focus only on the template-driven forms and validations.
Template-Driven Forms :
Definition:
Angular will create FormGrop and FormControl objects based on applied directives like ngForm, ngModel, ngModelGroup and template reference variables from a form template.

TEMPLATE DRIVEN DIRECTIVES:

All above directives are available once we import FormsModule into application NgModule.
NgForm – Will create a top-level FormGroup instance and binds it to a form to track updates/validations.
NgModel – Will Creates a FormControl instance and will track input field value, events and validation rules.it will keep view and model in sync.
NgModelGroup – Will create a sub-group form controller to maintain nested structured model.
Simply:

Example: Live Example

The this Keyword Runtime Semantics – Part 2

Every Function Object Has Methods apply(), call() and bind().
All these methods useful to call an arbitrary function as if this function was declared as a method on a target object.
[table class=”table table-striped”]
Function,Function invoke,Scope Change,How to call,Args passing
Apply(),Yes,Yes,”Function.prototype.apply ( thisArg, argArray )”,As Array[]
Call(),Yes,Yes,”Function.prototype.call (thisArg , …args)”,As comma separated
Bind(),No,Yes,”Function.prototype.bind ( thisArg , …args)”,As comma separated
[/table]

share = {
 a52WeekHigh:100,
 a52WeekLow:55,
 publishValue:function (demandIncrease){
 var d = demandIncrease? '. Demand Increase By:'+demandIncrease+'%' : ''
 console.log("52 week High and Low:"+ this.a52WeekHigh+'-'+this.a52WeekLow + d);
 }
}
pqrShare={
 a52WeekHigh:99,
 a52WeekLow:17
}
share.publishValue(); //52 week High and Low:100-55
share.publishValue.apply(pqrShare); //52 week High and Low:99-17
share.publishValue.apply(pqrShare,[12]);
share.publishValue.call(pqrShare,13);
var bond = share.publishValue.bind(pqrShare,14);//function won't invoke
bond(); //52 week High and Low:99-17. Demand Increase By:14%

Output will come as following:

52 week High and Low:100-55
52 week High and Low:99-17
52 week High and Low:99-17. Demand Increase By:12%
52 week High and Low:99-17. Demand Increase By:13%
52 week High and Low:99-17. Demand Increase By:14%