Repository: govuk-one-login/mobile-id-check-async
Pull request #626: [DCMAW-13162] Use real biometric credential package in issueBiometricCredential
Author: login: chris-cooksley-gds
Created: 2025-05-07T15:56:25Z | Merged: 2025-05-08T14:57:21Z

## Jira Ticket
<!-- Add your Jira ticket link here. -->
DCMAW-13162

## Description of changes
* Removes and gitignores .npmrc, replacing with a generator script - this will help prevent accidental PAT commits
* Ensures that this npmrc is used wherever an `npm install` is required in workflows, Dockerfile, etc
* Adds biometric credential package to dependencies
* Removes mock biometric credential code in favour of using real package - this required some type updates
* Updates README with instructions to allow installation of private packages
<!-- 
- Describe what changed and why
- Call out specific areas that need focus in the review
- Call out new patterns and articulate why they're being proposed
- For larger PRs, provide guidance on how best to start the review/understand the change
-->

## Review guidance
<details>
<summary>Review checklist</summary>

### Functional Review
- **Functionality**: Does it meet the acceptance criteria on the ticket and work as expected?
- **Requirements**: Does the code meet functional and non-functional requirements including compliance with programme standards and security gates?

### Security & Compliance
- **Personally Identifiable Information**: Is it possible for PII to be logged?
- **Security Considerations**: Are there any security implications that need to be addressed?

### Quality Assurance
- **Testing**: Is the code well-tested with sufficient coverage to provide confidence in correctness?
- **Edge Cases**: Have edge cases been considered and handled appropriately?

### Code Quality
- **Readability**: Is the code easy to understand for all team members, with clear naming and appropriate documentation?
- **Maintainability**: Is the code easy to change, reuse, and extend?
- **Code Style**: Does it follow our coding conventions and best practices?
- **Code Quality**: Is the code maintainable and following best practices? See [Values, Principles & Practices](https://govukverify.atlassian.net/wiki/spaces/DCMAW/pages/4955865126/ID+Check+Web+-+Values+Principles+and+Practices)

### Observability & Operations
- **Observability**: Are there appropriate logs/metrics that would help debug and monitor the service?
- **Performance**: Are there any performance considerations or potential bottlenecks?

### Documentation
- **Documentation**: Is the code well documented? Is there any existing documentation that needs updating?
- **Comments**: Are complex sections of code adequately commented if the intent is not clear?

### Review PR:
- **Title**: Contains ticket number and clear summary of change
- **Description**: Has clear description of change
</details>

## Evidence

* Unit tests passing
* API tests passing
* Local test container builds/runs successfully
* PR and push-to-main workflows succeed when tested on this branch (except for pushing sam artifact to build, which shouldn't work when done from a branch workflow)

<!-- 
- Provide evidence that changes work as expected
- For UI changes, include screenshots or recordings
- For API changes, include example requests/responses
- For infrastructure changes, include relevant logs or screenshots

Note: Leave this blank if no testing additional to the pull request workflows has been executed

When you should run the test container:
- If you've touched Docker configuration
- If you've added new environment variables
- For UI changes
-->

## Documentation
<!-- 
- Call out if key repository documentation has been updated
- Link to external documentation that has been updated

Note: Leave this blank if no documentation changes have been made
-->


Diff:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 45d0e24df..2d9951b05 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -40,6 +40,7 @@ updates:
     ignore:
       - dependency-name: "jose"
         update-types: ["version-update:semver-major"]
+      - dependency-name: "@govuk-one-login/mobile-id-check-biometric-credential"
   - package-ecosystem: "npm"
     directory: "test-resources"
     schedule:
diff --git a/.github/workflows/backend-api-pull-request.yml b/.github/workflows/backend-api-pull-request.yml
index a121d23d3..1615e7e2f 100644
--- a/.github/workflows/backend-api-pull-request.yml
+++ b/.github/workflows/backend-api-pull-request.yml
@@ -20,6 +20,9 @@ on:
 
   workflow_dispatch:
 
+permissions:
+  packages: read
+
 jobs:
   backend-api-pull-request:
     name: Backend-api pull request checks
@@ -28,4 +31,5 @@ jobs:
       ./.github/workflows/workflow_pull-request.yml
     with:
       WORKING_DIRECTORY: backend-api
+      PRIVATE_PACKAGES_REQUIRED: true
     secrets: inherit
diff --git a/.github/workflows/backend-api-push-to-main.yml b/.github/workflows/backend-api-push-to-main.yml
index 1fe8e08d5..b7bcaad21 100644
--- a/.github/workflows/backend-api-push-to-main.yml
+++ b/.github/workflows/backend-api-push-to-main.yml
@@ -13,6 +13,7 @@ on:
 permissions:
   contents: read
   id-token: write
+  packages: read
 
 jobs:
   sonar-scan:
@@ -36,6 +37,14 @@ jobs:
           cache: npm
           cache-dependency-path: backend-api/package-lock.json
 
+      - name: Configure authentication for private packages in .npmrc
+        run: |
+          echo "engine-strict=true" > .npmrc
+          echo "@govuk-one-login:registry=https://npm.pkg.github.com/" >> .npmrc
+          echo "//npm.pkg.github.com/:_authToken=$GITHUB_TOKEN" >> .npmrc
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
       - name: Install dependencies
         run: npm install
 
@@ -77,6 +86,14 @@ jobs:
         cache: npm
         cache-dependency-path: backend-api/package-lock.json
 
+    - name: Configure authentication for private packages in .npmrc
+      run: |
+        echo "engine-strict=true" > .npmrc
+        echo "@govuk-one-login:registry=https://npm.pkg.github.com/" >> .npmrc
+        echo "//npm.pkg.github.com/:_authToken=$GITHUB_TOKEN" >> .npmrc
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
     - name: Install Cosign
       uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a #v3.8.1
       with:
@@ -157,6 +174,14 @@ jobs:
           cache: npm
           cache-dependency-path: backend-api/package-lock.json
 
+      - name: Configure authentication for private packages in .npmrc
+        run: |
+          echo "engine-strict=true" > .npmrc
+          echo "@govuk-one-login:registry=https://npm.pkg.github.com/" >> .npmrc
+          echo "//npm.pkg.github.com/:_authToken=$GITHUB_TOKEN" >> .npmrc
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
       - name: Install Cosign
         uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a #v3.8.1
         with:
diff --git a/.github/workflows/job_ci-checks.yml b/.github/workflows/job_ci-checks.yml
index 80f8812c3..44187d18d 100644
--- a/.github/workflows/job_ci-checks.yml
+++ b/.github/workflows/job_ci-checks.yml
@@ -23,6 +23,10 @@ on:
         description: Path to working directory in repo
         required: true
         type: string
+      PRIVATE_PACKAGES_REQUIRED:
+        description: Whether private packages must be installed
+        type: string
+        default: false
 
 jobs:
   ci-checks:
@@ -47,6 +51,15 @@ jobs:
           cache-dependency-path: ${{ inputs.WORKING_DIRECTORY }}/package-lock.json
           node-version-file: ${{ inputs.WORKING_DIRECTORY }}/.nvmrc
 
+      - name: Configure authentication for private packages in .npmrc
+        if: inputs.PRIVATE_PACKAGES_REQUIRED == 'true'
+        run: |
+          echo "engine-strict=true" > .npmrc
+          echo "@govuk-one-login:registry=https://npm.pkg.github.com/" >> .npmrc
+          echo "//npm.pkg.github.com/:_authToken=$GITHUB_TOKEN" >> .npmrc
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
       - name: Install dependencies
         run: npm clean-install
 
diff --git a/.github/workflows/job_test-suite.yml b/.github/workflows/job_test-suite.yml
index 3c73b32cc..ebcfd4efa 100644
--- a/.github/workflows/job_test-suite.yml
+++ b/.github/workflows/job_test-suite.yml
@@ -19,6 +19,10 @@ on:
         description: Path to working directory in repo
         required: true
         type: string
+      PRIVATE_PACKAGES_REQUIRED:
+        description: Whether private packages must be installed
+        type: string
+        default: false
 
 jobs:
   run-test-suite:
@@ -41,6 +45,15 @@ jobs:
           cache-dependency-path: ${{ inputs.WORKING_DIRECTORY }}/package-lock.json
           node-version-file: ${{ inputs.WORKING_DIRECTORY }}/.nvmrc
 
+      - name: Configure authentication for private packages in .npmrc
+        if: inputs.PRIVATE_PACKAGES_REQUIRED == 'true'
+        run: |
+          echo "engine-strict=true" > .npmrc
+          echo "@govuk-one-login:registry=https://npm.pkg.github.com/" >> .npmrc
+          echo "//npm.pkg.github.com/:_authToken=$GITHUB_TOKEN" >> .npmrc
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
       - name: Install dependencies
         run: npm clean-install
 
diff --git a/.github/workflows/workflow_pull-request.yml b/.github/workflows/workflow_pull-request.yml
index b8e73ae2b..97766dce5 100644
--- a/.github/workflows/workflow_pull-request.yml
+++ b/.github/workflows/workflow_pull-request.yml
@@ -35,6 +35,10 @@ on:
         description: Path to working directory in repo
         required: true
         type: string
+      PRIVATE_PACKAGES_REQUIRED:
+        description: Whether private packages must be installed
+        type: string
+        default: false
     secrets:
       SONAR_TOKEN:
         description: The token used for secure access to the SonarQube platform
@@ -83,6 +87,7 @@ jobs:
       RUN_RAIN_FORMAT_VERIFY: ${{ inputs.RUN_RAIN_FORMAT_VERIFY }}
       RUN_SAM_VALIDATE: ${{ inputs.RUN_SAM_VALIDATE }}
       WORKING_DIRECTORY: ${{ inputs.WORKING_DIRECTORY }}
+      PRIVATE_PACKAGES_REQUIRED: ${{ inputs.PRIVATE_PACKAGES_REQUIRED }}
 
   run-test-suite:
     name: Run test suite
@@ -94,6 +99,7 @@ jobs:
       RUN_UNIT_TESTS: ${{ inputs.RUN_UNIT_TESTS }}
       RUN_SONARQUBE: ${{ inputs.RUN_SONARQUBE }}
       WORKING_DIRECTORY: ${{ inputs.WORKING_DIRECTORY }}
+      PRIVATE_PACKAGES_REQUIRED: ${{ inputs.PRIVATE_PACKAGES_REQUIRED }}
 
   sonarqube-scan:
     name: SonarQube scan
diff --git a/.gitignore b/.gitignore
index ab7a6f1cc..fbefa545e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,6 @@ parameters-*-cf-dist.json
 # files generated when running the sts-mock jwks helper script
 jwks.json
 private-key.json
+
+# NPMRC files
+**/.npmrc
diff --git a/backend-api/.npmrc b/backend-api/.npmrc
deleted file mode 100644
index 4fd021952..000000000
--- a/backend-api/.npmrc
+++ /dev/null
@@ -1 +0,0 @@
-engine-strict=true
\ No newline at end of file
diff --git a/backend-api/Dockerfile b/backend-api/Dockerfile
index b5300e493..321f471ae 100644
--- a/backend-api/Dockerfile
+++ b/backend-api/Dockerfile
@@ -1,3 +1,5 @@
+# syntax=docker/dockerfile:1
+
 FROM node:22-alpine
 
 WORKDIR /backend-api
@@ -5,8 +7,8 @@ WORKDIR /backend-api
 # Create a new user 'test' to avoid running the app as a root
 RUN adduser --disabled-password test && chown test .
 
-COPY package.json package-lock.json ./
-RUN npm install --ignore-scripts
+COPY package.json package-lock.json .npmrc ./
+RUN --mount=type=secret,id=NPM_TOKEN,env=NPM_TOKEN npm install --ignore-scripts
 
 
 ## Update container, install awscli and check awscli is installed correctly
diff --git a/backend-api/README.md b/backend-api/README.md
index abf6e4792..36be84618 100644
--- a/backend-api/README.md
+++ b/backend-api/README.md
@@ -17,6 +17,34 @@
 
 ## Setup
 
+### Enable the installation of modules from GitHub Packages
+
+1. On GitHub, generate a Personal Access Token (classic) with the following scopes:
+
+    - `read:packages`
+    - `repo`
+
+   It is recommended to:
+    - create a new PAT with only the required scopes, to ensure least-privilege
+    - store this PAT _securely_ for re-use in the OS keychain to avoid needing to generate a new PAT for each install
+
+1. Set your token as an environment variable
+
+    ```zsh
+    export NPM_TOKEN=<your-classic-pat>
+    ```
+
+1. Use script to create a `.npmrc` file referencing this environment variable:
+
+    ```bash
+    # From /backend-api
+    ./generate_npmrc.sh
+    ```
+
+**NOTE:** the above steps must be followed before installing dependencies (`npm install`).
+
+### Pre-commit hooks
+
 This repository uses Githooks to run pre-commit checks. To install these:
 
 ```bash
diff --git a/backend-api/generate_npmrc.sh b/backend-api/generate_npmrc.sh
new file mode 100755
index 000000000..034ef3255
--- /dev/null
+++ b/backend-api/generate_npmrc.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -eu
+
+{
+  echo 'engine-strict=true'
+  echo '@govuk-one-login:registry=https://npm.pkg.github.com/'
+  echo '//npm.pkg.github.com/:_authToken=${NPM_TOKEN}'
+} > .npmrc
\ No newline at end of file
diff --git a/backend-api/package-lock.json b/backend-api/package-lock.json
index 4800a0702..3187aa5c1 100644
--- a/backend-api/package-lock.json
+++ b/backend-api/package-lock.json
@@ -19,6 +19,7 @@
         "@aws-sdk/client-ssm": "^3.803.0",
         "@aws-sdk/node-http-handler": "3.374.0",
         "@aws-sdk/util-dynamodb": "^3.803.0",
+        "@govuk-one-login/mobile-id-check-biometric-credential": "1.0.0",
         "axios": "1.9.0",
         "ecdsa-sig-formatter": "1.0.11",
         "esbuild": "^0.25.4",
@@ -5989,6 +5990,14 @@
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       }
     },
+    "node_modules/@govuk-one-login/mobile-id-check-biometric-credential": {
+      "version": "1.0.0",
+      "resolved": "https://npm.pkg.github.com/download/@govuk-one-login/mobile-id-check-biometric-credential/1.0.0/b2dab5399962cdf9313b903b668172e61862d99e",
+      "integrity": "sha512-/Dbqjc4Wx2n8XmKqdkRD4gbdZPYWxzQsk5ucONiLzrY8A6zKxbmtIpiEPuRgxbTTZ1fPIwHewMMglEzA1+CuRA==",
+      "engines": {
+        "node": "22"
+      }
+    },
     "node_modules/@humanfs/core": {
       "version": "0.19.1",
       "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
diff --git a/backend-api/package.json b/backend-api/package.json
index 4c7177923..a5834c18b 100644
--- a/backend-api/package.json
+++ b/backend-api/package.json
@@ -61,6 +61,7 @@
     "@aws-sdk/client-ssm": "^3.803.0",
     "@aws-sdk/node-http-handler": "3.374.0",
     "@aws-sdk/util-dynamodb": "^3.803.0",
+    "@govuk-one-login/mobile-id-check-biometric-credential": "1.0.0",
     "axios": "1.9.0",
     "ecdsa-sig-formatter": "1.0.11",
     "esbuild": "^0.25.4",
diff --git a/backend-api/run-tests-locally.sh b/backend-api/run-tests-locally.sh
index 6a0b52539..de62c2f01 100755
--- a/backend-api/run-tests-locally.sh
+++ b/backend-api/run-tests-locally.sh
@@ -30,10 +30,11 @@ awk '{ printf("CFN_%s=\"%s\"\n", $1, $2) }' cf-output.txt >> docker-vars.env
 
 aws configure export-credentials --format env-no-export >> docker-vars.env
 
-docker build --tag testcontainer .
+docker buildx build --tag testcontainer --secret id=NPM_TOKEN,env=NPM_TOKEN .
 
 docker run --rm --interactive --tty \
   --user root \
   --env-file docker-vars.env \
+  --env NPM_TOKEN=$NPM_TOKEN \
   --volume "$(pwd):/results" \
   testcontainer
diff --git a/backend-api/src/functions/adapters/aws/kms/createKmsSignedJwt/createKmsSignedJwt.test.ts b/backend-api/src/functions/adapters/aws/kms/createKmsSignedJwt/createKmsSignedJwt.test.ts
index f66ada0b1..9388dad44 100644
--- a/backend-api/src/functions/adapters/aws/kms/createKmsSignedJwt/createKmsSignedJwt.test.ts
+++ b/backend-api/src/functions/adapters/aws/kms/createKmsSignedJwt/createKmsSignedJwt.test.ts
@@ -11,6 +11,8 @@ import { expect } from "@jest/globals";
 import "../../../../../../tests/testUtils/matchers";
 import { emptyFailure, Result, successResult } from "../../../../utils/result";
 import { mockDerSignature } from "../../../../testUtils/unitTestData";
+import { JwtPayload } from "../../../../types/jwt";
+import { BiometricCredential } from "@govuk-one-login/mobile-id-check-biometric-credential";
 
 describe("createKmsSignedJwt", () => {
   let consoleDebugSpy: jest.SpyInstance;
@@ -23,13 +25,13 @@ describe("createKmsSignedJwt", () => {
   let result: Result<string, void>;
 
   const mockKid = "mockKid";
-  const mockPayload = {
+  const mockPayload: JwtPayload = {
     iat: 123,
     iss: "mockIss",
     jti: "mockJti",
     nbf: 123,
     sub: "mockSub",
-    vc: "mockVc",
+    vc: {} as BiometricCredential,
   };
 
   beforeEach(() => {
@@ -119,7 +121,7 @@ describe("createKmsSignedJwt", () => {
       it("Returns successResult with signed JWT", () => {
         expect(result).toEqual(
           successResult(
-            "eyJhbGciOiJFUzI1NiIsImtpZCI6Im1vY2tLaWQiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjEyMywiaXNzIjoibW9ja0lzcyIsImp0aSI6Im1vY2tKdGkiLCJuYmYiOjEyMywic3ViIjoibW9ja1N1YiIsInZjIjoibW9ja1ZjIn0.QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkIkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJA",
+            "eyJhbGciOiJFUzI1NiIsImtpZCI6Im1vY2tLaWQiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjEyMywiaXNzIjoibW9ja0lzcyIsImp0aSI6Im1vY2tKdGkiLCJuYmYiOjEyMywic3ViIjoibW9ja1N1YiIsInZjIjp7fX0.QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkIkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJA",
           ),
         );
       });
diff --git a/backend-api/src/functions/asyncIssueBiometricCredential/asyncIssueBiometricCredentialHandler.ts b/backend-api/src/functions/asyncIssueBiometricCredential/asyncIssueBiometricCredentialHandler.ts
index 5479425f9..d2dcdce1d 100644
--- a/backend-api/src/functions/asyncIssueBiometricCredential/asyncIssueBiometricCredentialHandler.ts
+++ b/backend-api/src/functions/asyncIssueBiometricCredential/asyncIssueBiometricCredentialHandler.ts
@@ -37,15 +37,16 @@ import {
 } from "../common/session/session";
 import { GetBiometricSessionError } from "./getBiometricSession/getBiometricSession";
 import { GetSessionIssueBiometricCredential } from "../common/session/getOperations/IssueBiometricCredential/GetSessionIssueBiometricCredential";
+import { ResultSent } from "../common/session/updateOperations/ResultSent/ResultSent";
+import { CredentialJwtPayload } from "../types/jwt";
+import { randomUUID } from "crypto";
 import {
+  BiometricCredential,
+  FraudCheckData,
   GetCredentialError,
   GetCredentialErrorCode,
   GetCredentialOptions,
-  FraudCheckData,
-} from "./mockGetCredentialFromBiometricSession/types";
-import { ResultSent } from "../common/session/updateOperations/ResultSent/ResultSent";
-import { CredentialJwtPayload } from "../types/jwt";
-import { randomUUID } from "crypto";
+} from "@govuk-one-login/mobile-id-check-biometric-credential";
 
 export async function lambdaHandlerConstructor(
   dependencies: IssueBiometricCredentialDependencies,
@@ -490,7 +491,7 @@ const handleUpdateSessionError = async (
   if (writeEventResult.isError) logErrorWritingErrorEvent();
 };
 export const buildCredentialJwtPayload = (jwtData: {
-  credential: string;
+  credential: BiometricCredential;
   sub: string;
   issuer: string;
 }): CredentialJwtPayload => {
diff --git a/backend-api/src/functions/asyncIssueBiometricCredential/handlerDependencies.ts b/backend-api/src/functions/asyncIssueBiometricCredential/handlerDependencies.ts
index aa41aaba1..2ea190565 100644
--- a/backend-api/src/functions/asyncIssueBiometricCredential/handlerDependencies.ts
+++ b/backend-api/src/functions/asyncIssueBiometricCredential/handlerDependencies.ts
@@ -11,10 +11,12 @@ import { sendMessageToSqs } from "../adapters/aws/sqs/sendMessageToSqs";
 import { SQSMessageBody } from "../adapters/aws/sqs/types";
 import { IEventService } from "../services/events/types";
 import { EventService } from "../services/events/eventService";
-import { mockGetCredentialFromBiometricSession } from "./mockGetCredentialFromBiometricSession/mockGetCredentialFromBiometricSession";
-import { IGetCredentialFromBiometricSession } from "./mockGetCredentialFromBiometricSession/types";
 import { createKmsSignedJwt } from "../adapters/aws/kms/createKmsSignedJwt/createKmsSignedJwt";
 import { CreateKmsSignedJwt } from "../adapters/aws/kms/createKmsSignedJwt/types";
+import {
+  getCredentialFromBiometricSession,
+  IGetCredentialFromBiometricSession,
+} from "@govuk-one-login/mobile-id-check-biometric-credential";
 
 export type IssueBiometricCredentialDependencies = {
   env: NodeJS.ProcessEnv;
@@ -37,6 +39,6 @@ export const runtimeDependencies: IssueBiometricCredentialDependencies = {
   getBiometricSession,
   getEventService: (sqsQueue: string) => new EventService(sqsQueue),
   sendMessageToSqs,
-  getCredentialFromBiometricSession: mockGetCredentialFromBiometricSession,
+  getCredentialFromBiometricSession,
   createKmsSignedJwt,
 };
diff --git a/backend-api/src/functions/asyncIssueBiometricCredential/mockGetCredentialFromBiometricSession/mockGetCredentialFromBiometricSession.ts b/backend-api/src/functions/asyncIssueBiometricCredential/mockGetCredentialFromBiometricSession/mockGetCredentialFromBiometricSession.ts
deleted file mode 100644
index 2983ac75d..000000000
--- a/backend-api/src/functions/asyncIssueBiometricCredential/mockGetCredentialFromBiometricSession/mockGetCredentialFromBiometricSession.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { successResult } from "../../utils/result";
-import { IGetCredentialFromBiometricSession } from "./types";
-
-export const mockGetCredentialFromBiometricSession: IGetCredentialFromBiometricSession =
-  (_vendorResponse, _fraudCheckData, _options) => {
-    return successResult({
-      credential: "mockCredential",
-      advisories: "mockAdvisories",
-      audit: "mockAudit",
-      analytics: "mockAnalytics",
-    });
-  };
diff --git a/backend-api/src/functions/asyncIssueBiometricCredential/mockGetCredentialFromBiometricSession/types.ts b/backend-api/src/functions/asyncIssueBiometricCredential/mockGetCredentialFromBiometricSession/types.ts
deleted file mode 100644
index 4c0768867..000000000
--- a/backend-api/src/functions/asyncIssueBiometricCredential/mockGetCredentialFromBiometricSession/types.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Result } from "../../utils/result";
-
-export type IGetCredentialFromBiometricSession = (
-  biometricSession: unknown,
-  fraudCheckData: FraudCheckData,
-  options: GetCredentialOptions,
-) => Result<BiometricCredentialWrapper, GetCredentialError>;
-
-export interface FraudCheckData {
-  userSessionCreatedAt: number;
-  opaqueId: string;
-}
-
-export interface GetCredentialOptions {
-  enableUtopiaTestDocument: boolean;
-  enableDrivingLicence: boolean;
-  enableNfcPassport: boolean;
-  enableBiometricResidencePermit: boolean;
-  enableBiometricResidenceCard: boolean;
-}
-
-export interface BiometricCredentialWrapper {
-  credential: string;
-  analytics: string;
-  audit: string;
-  advisories: string;
-}
-
-export interface GetCredentialError {
-  errorCode: GetCredentialErrorCode;
-  errorReason: GetCredentialErrorReason;
-  data: object;
-}
-
-export enum GetCredentialErrorCode {
-  PARSE_FAILURE = "BIOMETRIC_SESSION_PARSE_FAILURE",
-  VENDOR_LIKENESS_DISABLED = "VENDOR_LIKENESS_DISABLED",
-  BIOMETRIC_SESSION_NOT_VALID = "BIOMETRIC_SESSION_NOT_VALID",
-  SUSPECTED_FRAUD = "SUSPECTED_FRAUD",
-}
-
-export enum GetCredentialErrorReason {
-  UNEXPECTED_DOCUMENT_TYPE = "UNEXPECTED_DOCUMENT_TYPE",
-  MOBILE_ERROR = "MOBILE_BROKE_MESSAGE_CONTRACT",
-  VENDOR_ERROR = "VENDOR_BROKE_MESSAGE_CONTRACT",
-  BIOMETRIC_SESSION_OLDER_THAN_AUTH_SESSION = "BIOMETRIC_SESSION_OLDER_THAN_AUTH_SESSION",
-  BIOMETRIC_SESSION_OPAQUEID_MISMATCH = "BIOMETRIC_SESSION_OPAQUEID_MISMATCH",
-}
diff --git a/backend-api/src/functions/types/jwt.ts b/backend-api/src/functions/types/jwt.ts
index 27c0cde49..2b57d66fa 100644
--- a/backend-api/src/functions/types/jwt.ts
+++ b/backend-api/src/functions/types/jwt.ts
@@ -1,3 +1,5 @@
+import { BiometricCredential } from "@govuk-one-login/mobile-id-check-biometric-credential";
+
 export interface IJwtPayload {
   [key: string]: string | number | undefined;
   iss: string;
@@ -21,7 +23,7 @@ export interface CredentialJwtPayload {
   jti: string;
   nbf: number;
   sub: string;
-  vc: string; // To be updated when using the real package
+  vc: BiometricCredential;
 }
 
 export type JwtPayload = CredentialJwtPayload;


Reviews:
user: login: jmooney-dd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T11:35:31Z
user: login: jmooney-dd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T11:56:39Z
user: login: kikidawson-gds
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T12:02:36Z
user: login: sandymay-dd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T12:07:24Z
user: login: chris-cooksley-gds
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T12:48:37Z
user: login: jmooney-dd
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T14:48:51Z
user: login: kikidawson-gds
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T14:55:15Z

Review comments:
diff_hunk: @@ -23,6 +23,10 @@ on:
         description: Path to working directory in repo
         required: true
         type: string
+      PRIVATE_PACKAGES_REQUIRED:
+        description: Whether private packages must be installed
+        type: string
+        default: false
path: .github/workflows/job_ci-checks.yml
user: login: jmooney-dd
body: question: should the default be true since this private dep will always be needed to run our system correctly?
created_at: 2025-05-08T11:35:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,6 +17,34 @@
 
 ## Setup
 
+### Enable the installation of modules from GitHub Packages
path: backend-api/README.md
user: login: jmooney-dd
body: praise: followed these instructions and the install worked immediately 🚀 
created_at: 2025-05-08T11:56:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,6 +87,7 @@ jobs:
       RUN_RAIN_FORMAT_VERIFY: ${{ inputs.RUN_RAIN_FORMAT_VERIFY }}
path: .github/workflows/workflow_pull-request.yml
user: login: kikidawson-gds
body: Please could you add the new input to the validate-inputs step so the value can get printed out. 

Don't looove that so many changes need to be made for this one change. It's definitely something we can make better in the future.
created_at: 2025-05-08T12:02:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23,6 +23,10 @@ on:
         description: Path to working directory in repo
         required: true
         type: string
+      PRIVATE_PACKAGES_REQUIRED:
+        description: Whether private packages must be installed
+        type: string
+        default: false
path: .github/workflows/job_ci-checks.yml
user: login: sandymay-dd
body: This shared job is used by multiple workflows across different SAM applications (the test resources app, for example). The other apps don't need this step :) 
created_at: 2025-05-08T12:07:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,6 +87,7 @@ jobs:
       RUN_RAIN_FORMAT_VERIFY: ${{ inputs.RUN_RAIN_FORMAT_VERIFY }}
path: .github/workflows/workflow_pull-request.yml
user: login: chris-cooksley-gds
body: Don't love it either, but it's a consequence of gitignoring the npmrc, and I think that's still worth it for the security benefits. Given that we have a lot of different actions that need to `npm install`, I think there will always be an element of repetition in providing the necessary credentials for that operation - although if we can reference some sort of shared step that would help a bit.
created_at: 2025-05-08T12:48:37Z
author_association: CONTRIBUTOR

PR comments:
user: login: jmooney-dd
created_at: 2025-05-08T11:55:07Z
body: potential issue: I ran the run-tests-locally script against your cc-async-backend and I got around 21 failures. HOWEVER:
- I am not certain this is even the stack you're using to test these changes
- I can see this stack rolled back today so unsure if all changes are present

Could you provide any more info re this?
author_association: CONTRIBUTOR
user: login: chris-cooksley-gds
created_at: 2025-05-08T12:50:43Z
body: > potential issue: I ran the run-tests-locally script against your cc-async-backend and I got around 21 failures. HOWEVER:
> 
> * I am not certain this is even the stack you're using to test these changes
> * I can see this stack rolled back today so unsure if all changes are present
> 
> Could you provide any more info re this?

That stack hasn't got my latest changes as it failed to deploy. Doesn't look like it was anything to do with my change (I think it was a KMS key that wouldn't update or something) - it's been a while since I've updated that stack so I'm deleting and deploying a fresh one now. Feel free to wait for API test evidence before giving the tick - just wanted to get early eyes on the bulk of the changes in the meantime :) I'd be surprised if there were any changes made here that affected our API tests given we don't have any for this Lambda.
author_association: CONTRIBUTOR
user: login: chris-cooksley-gds
created_at: 2025-05-08T14:47:51Z
body: > > potential issue: I ran the run-tests-locally script against your cc-async-backend and I got around 21 failures. HOWEVER:
> > 
> > * I am not certain this is even the stack you're using to test these changes
> > * I can see this stack rolled back today so unsure if all changes are present
> > 
> > Could you provide any more info re this?
> 
> That stack hasn't got my latest changes as it failed to deploy. Doesn't look like it was anything to do with my change (I think it was a KMS key that wouldn't update or something) - it's been a while since I've updated that stack so I'm deleting and deploying a fresh one now. Feel free to wait for API test evidence before giving the tick - just wanted to get early eyes on the bulk of the changes in the meantime :) I'd be surprised if there were any changes made here that affected our API tests given we don't have any for this Lambda.

Redeployed, API tests passing.
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-05-08T14:49:46Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=mobile-id-check-async-sts-mock&pullRequest=626) **Quality Gate passed for 'mobile-id-check-async-sts-mock'**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=mobile-id-check-async-sts-mock&pullRequest=626&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=mobile-id-check-async-sts-mock&pullRequest=626&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=mobile-id-check-async-sts-mock&pullRequest=626&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=mobile-id-check-async-sts-mock&pullRequest=626&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=mobile-id-check-async-sts-mock&pullRequest=626&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=mobile-id-check-async-sts-mock&pullRequest=626)


author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-08T14:50:48Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=mobile-id-check-async&pullRequest=626) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=mobile-id-check-async&pullRequest=626&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=mobile-id-check-async&pullRequest=626&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=mobile-id-check-async&pullRequest=626&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=mobile-id-check-async&pullRequest=626&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=mobile-id-check-async&pullRequest=626&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=mobile-id-check-async&pullRequest=626)


author_association: NONE