Skip to content

feat(RPS): MS authorizer service & Postcode Lookup Service - #607

Open
marco-ukhsa wants to merge 6 commits into
v2from
feat/apim-address-lookup-service
Open

marco-ukhsa wants to merge 6 commits into
v2from
feat/apim-address-lookup-service

Conversation

@marco-ukhsa

@marco-ukhsa marco-ukhsa commented May 15, 2026

Copy link
Copy Markdown
Collaborator

Address lookup service & msal authorizer service

Introduces two new services for address lookup via APIM and shared MSAL authentication.

Changes

msalAuthorizerService.ts

  • Fetches Azure AD client credentials bearer token into a reusable class
  • RequirestenantId, clientId, clientSecret, and scopes from caller
  • Exposes getToken() method which returns the Bearer token string

addressLookupService.ts

  • Exposes lookupByPostcode() against the agency's APIM /matchAddress endpoint
  • Supports adding maxResults, fuzzy, and dataset options or leaving as default
  • Uses msalAuthorizerService for fetching and using Bearer token
  • Optionally sends Ocp-Apim-Subscription-Key header only if the subscription key is provided from caller

Notes

  • AddressLookupConfig extends MsalAuthorizerConfig so the auth config is passed through a single object

@marco-ukhsa
marco-ukhsa requested a review from kitttang May 15, 2026 10:04
@marco-ukhsa
marco-ukhsa requested a review from a team as a code owner May 15, 2026 10:04
@marco-ukhsa marco-ukhsa changed the title MS authorizer service & Postcode Lookup Service Feat(RPS): MS authorizer service & Postcode Lookup Service May 15, 2026
@marco-ukhsa marco-ukhsa changed the title Feat(RPS): MS authorizer service & Postcode Lookup Service feat(RPS): MS authorizer service & Postcode Lookup Service May 15, 2026
@marco-ukhsa
marco-ukhsa force-pushed the feat/apim-address-lookup-service branch from b2331f6 to 5ace378 Compare May 15, 2026 10:39
kitttang
kitttang previously approved these changes May 20, 2026
@Dashue

Dashue commented May 28, 2026

Copy link
Copy Markdown

Hi @marco-ukhsa, the code is looking good

On initial review I have the below observations

Code Todo

[] Address Type missing (currently 'unknown')

Questions

Multi tenant support

[] Does the current implementation give multi tenant support (given a single deployed server instance is hosting multiple forms which may need their own MSAL configuration)

[] With this being an inheritance based design, is the multi tenant support facilitated through a per form IOC registration of the addressLookupService?

Default values

[] Do default values make sense from a technical standpoint or only suitable to RPS usage maxResults = 100, fuzzy = false, dataset = "DPA"

Token

[] Lifecycle / TTL - Looks like per address lookup, is this per-call approach future proof (thinking other future services and calls requiring MSAL, like secure form submissions?)
[] Expiration and Refreshing (If per lookup, then not a concern)
[] Error handling and Retry logic not needed? (If per lookup, then less of an issue)

Missing documentation

Some possible ideas below that would be helpful (can be placed in docs/runner?)

Use cases

[] DI registration
[] Consumer usage / implementation

Configuration

[] MSAL variables (are they form level?)

Multi tenant support

[] How it's facilitated

Token

[] Lifetime / Lifecycle / TTL

@marco-ukhsa

Copy link
Copy Markdown
Collaborator Author

Hi @marco-ukhsa, the code is looking good

On initial review I have the below observations

Code Todo

[] Address Type missing (currently 'unknown')

Questions

Multi tenant support

[] Does the current implementation give multi tenant support (given a single deployed server instance is hosting multiple forms which may need their own MSAL configuration)

[] With this being an inheritance based design, is the multi tenant support facilitated through a per form IOC registration of the addressLookupService?

Default values

[] Do default values make sense from a technical standpoint or only suitable to RPS usage maxResults = 100, fuzzy = false, dataset = "DPA"

Token

[] Lifecycle / TTL - Looks like per address lookup, is this per-call approach future proof (thinking other future services and calls requiring MSAL, like secure form submissions?) [] Expiration and Refreshing (If per lookup, then not a concern) [] Error handling and Retry logic not needed? (If per lookup, then less of an issue)

Missing documentation

Some possible ideas below that would be helpful (can be placed in docs/runner?)

Use cases

[] DI registration [] Consumer usage / implementation

Configuration

[] MSAL variables (are they form level?)

Multi tenant support

[] How it's facilitated

Token

[] Lifetime / Lifecycle / TTL

Hi @Dashue, thank you for the fast review!
I just wanted to address your comments, to see if this clears everything up, please let me know your thoughts so I can complete the remaining tasks:

Code Todo

  • Address Type - will be updated in the next commit to address the unknown

Multi-tenant support

  • Yes, each AddressLookupService instance takes its own AddressLookupConfig which extends MsalAuthorizerConfig, so each form/tenant can supply its own tenantId, clientId, clientSecret and scopes with no shared state between instances
  • Correct, multi-tenancy is facilitated through per-form IoC registration, each form registers its own AddressLookupService with its own config. Will add a DI registration example to the docs to make this clearer

Default values

  • These aren't RPS specific but the values that really make sense for this use case:
    • dataset = "DPA" - we're looking up by UDPRN value which is a DPA identifier, so LPI isn't relevant for this service
    • fuzzy = false - postcodes are exact strings so fuzzy matching doesn't really apply in this case
    • maxResults = 100 - this is actually the hard limit of the lookup service, anything over 100 would fail at the API level, so I've defaulted to the max to avoid unexpected errors for callers.
      These values can also be added from the caller script, so these are more just placeholders for the most likely payload. I'm happy to remove if you think that would be a cleaner approach.

Token

  • MSAL's acquireTokenByClientCredential handles caching internally, tokens are reused until near expiry so this isn't a network call per lookup
  • Given the above, expiration and refresh is handled by MSAL so no concern there
  • Same applies to retry logic, less of a concern given MSAL handles the token lifecycle, but open to adding it if there's a specific failure scenario you'd like to address.

Missing documentation

  • Will be added in the next commit covering:
    • DI registration and usage examples
    • MSAL config variables and their scope (form level)
    • How multi-tenancy is facilitated

@Dashue

Dashue commented Jun 1, 2026

Copy link
Copy Markdown

Hi @marco-ukhsa, that's perfect!
As expected, I was just missing some parts of the puzzle

I think your notes on Default values and Token are really insightful and worth including in the documentation as well?

@marco-ukhsa

Copy link
Copy Markdown
Collaborator Author

Hi @marco-ukhsa, that's perfect! As expected, I was just missing some parts of the puzzle

I think your notes on Default values and Token are really insightful and worth including in the documentation as well?
@Dashue
great shout - makes perfect sense, I've added the md doc to include these and the previous comment, and included the api response shape.

please let me know if there's anything else that can I can add to improve this service

@Dashue

Dashue commented Jun 3, 2026

Copy link
Copy Markdown

Hi @marco-ukhsa thanks for the updates

Hope the below makes sense, let me know if I've misunderstood anything

DI registration to support multi tenancy
The example doesn't seem to support multi tenancy with it being one instance per form and use variables from process.env

I believe the approach would need to register a named instance per form that depend on the location service
Something along the lines of:

const forms: FormConfiguration[] = configureEnginePlugin(
    formFileName,
    formFilePath,
    options
  );

  const locationServiceInstances = [];
  for (const form of forms) {
    if (form.configuration.locationService.apimBaseUrl) {
      // form.id comes from idFromFilename in runner\src\server\plugins\engine\helpers.ts
      //   id = filename.replace(/govsite\.|\.json|/gi, "");
      const addressLookupInstanceName = `locationServiceInstance:${form.id}`; 
      locationServiceInstances.push(
        Schmervice.withName(addressLookupInstanceName, AddressLookupService)
      );

      // Or instead of gathering a list just do multiple registrations in the this iteration:
     // await server.registerService(Schmervice.withName(addressLookupInstanceName, AddressLookupService));
    }
  }

  if (locationServiceInstances.length) {
    await server.registerService(locationServiceInstances);
  }

Extend @xgovformbuilder/model with new form level configuration
model\src\data-model\types.ts
model\src\schema\schema.ts

Separate doc for MsalAuthorizer
I wonder if it makes sense to break out the documentation for MsalAuthorizer and reference it from the addressLookupService docs

I'm working on the APIM support for secure file uploads and I think we can take a similar to AddressLookupService and re-use the MsalAuthorizerService. Which at that point the secure form submission documentation could then similarly just reference the MsalAuthorizer/MsalAuthorizerService documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants