Search code examples
typescriptazureauthenticationazure-functions

Why is my x-functions-key Azure Function header not received?


I have an Azure Function ("version": "[4.0.0, 5.0.0)") written in Typescript and I'm trying to get EasyAuth to work.

I have 2 functions: exec() and login(). exec() has function level authentication set, and login() has anonymous level authentication set.

Both work as expected with exec() returning a 401 error, and login() returning a 200 success response.

When testing in Postman, if I add the host level default function key in the Url using the ?code=<key> the exec() method authenticates correctly, and I get a 200 success response (as expected).

However, I want to use the header option to authenticate, and have added the x-functions-key header tag, using the same host-level function key that was used in the URL parameter test. I also want to pass in an additional name/value pair in the header and access that within the function too. The steps I follow are consistent with this tutorial:

http://dontcodetired.com/blog/post/Azure-HTTP-Function-Authorization-with-Function-Keys

and

https://damienbod.com/2020/08/17/securing-azure-functions-using-api-keys/

I've even created a new custom key with the same results as above (both successful URL parameter test and failed header x-functions-key test).

I suspect the header is being filtered out before it reaches the Azure Function. To test this, I make the same header-based call to login() and write the context and request objects to context.log:

2023-08-22T20:06:47Z   [Information]   Request!!!! HttpRequest { query: URLSearchParams {}, params: {} }
2023-08-22T20:06:47Z   [Information]   context!!!! InvocationContext {
  invocationId: 'f05f6245-b681-4aad-bff6-60fcbe3b68de',
  functionName: 'login',
  extraInputs: InvocationContextExtraInputs {},
  extraOutputs: InvocationContextExtraOutputs {},
  retryContext: undefined,
  traceContext: {
    traceParent: '00-c92a4b542b0bb1a0e4635361dfc636ff-703d73ec197fc2e1-00',
    traceState: '',
    attributes: {}
  },
  triggerMetadata: undefined,
  options: {
    trigger: {
      authLevel: 'anonymous',
      methods: [Array],
      route: undefined,
      type: 'httpTrigger',
      name: 'httpTrigger3',
      direction: 'in'
    },
    return: { type: 'http', name: '$return', direction: 'out' },
    extraInputs: [],
    extraOutputs: []
  }
}
2023-08-22T20:06:47Z   [Information]   Executed 'Functions.login' (Succeeded, Id=f05f6245-b681-4aad-bff6-60fcbe3b68de, Duration=66ms)

In this output, I would expect to see the x-functions-key and my other hello/world header value in the above output.

I did find an article describing what might be a solution:

Why is my header data missing from my Azure Function Http Trigger in .Net 5 when calling from HttpClient.GetAsync

I've added the Access-Control-Expose-Headers to my host.json and redeployed but it made no difference. E.g.:

  "extensions": {
    "http": {
      "customHeaders": {
        "Access-Control-Expose-Headers": "*"
      }
    }
  }

Why isn't Azure Function EasyAuth working with the x-functions-key (where it works with the URL parameter option)?

... and why aren't the header values coming through to the login() function at all?

------ UPDATE -------

OK, I've got my non x-functions-key header coming through now. I discovered (using Typescripts auto-complete function in VS Code) that there are additional members that you can interrogate from the request):

context.log('Request.headers', request.headers);

Produces the following output:

2023-08-22T22:48:07Z   [Information]   Request.headers HeadersList {
  cookies: null,
  [Symbol(headers map)]: Map(24) {
    'accept' => { name: 'accept', value: '*/*' },
    'host' => { name: 'host', value: 'fxa-sitemap40-dev-eas.azurewebsites.net' },
    'max-forwards' => { name: 'max-forwards', value: '8' },
    'user-agent' => { name: 'user-agent', value: 'PostmanRuntime/7.32.3' },
    'traceparent' => {
      name: 'traceparent',
      value: '00-4ef804db4293ee5ddc9810ed3b690143-b390f556c4d90053-00'
    },
    'aiden-test' => { name: 'aiden-test', value: 'hoihoi' },
    'postman-token' => {
      name: 'postman-token',
      value: '23456f99-8979-4ee4-860e-f1a4cba067cf'
    },
    'x-arr-log-id' => {
      name: 'x-arr-log-id',
      value: '87e6b362-02d7-4b9b-87bb-a91e57601d52'
    },
    'client-ip' => { name: 'client-ip', value: '10.0.32.9:61024' },
    'x-site-deployment-id' => { name: 'x-site-deployment-id', value: 'fxa-sitemap40-dev-eas' },
    'was-default-hostname' => {
      name: 'was-default-hostname',
      value: 'fxa-xxxxxxxx-dev-eas.azurewebsites.net'
    },
    'x-forwarded-proto' => { name: 'x-forwarded-proto', value: 'https' },
    'x-appservice-proto' => { name: 'x-appservice-proto', value: 'https' },
    'x-arr-ssl' => {
      name: 'x-arr-ssl',
      value: '2048|256|CN=Microsoft Azure TLS Issuing CA 01, O=Microsoft Corporation, C=US|CN=*.azurewebsites.net, O=Microsoft Corporation, L=Redmond, S=WA, C=US'
    },
    'x-forwarded-tlsversion' => { name: 'x-forwarded-tlsversion', value: '1.2' },
    'x-forwarded-for' => {
      name: 'x-forwarded-for',
      value: '1.145.212.27:2907, 13.75.34.193:29415'
    },
    'x-original-url' => { name: 'x-original-url', value: '/api/login' },
    'x-waws-unencoded-url' => { name: 'x-waws-unencoded-url', value: '/api/login' },
    'x-ms-original-url' => {
      name: 'x-ms-original-url',
      value: 'https://xxx.yyyy.app/api/login'
    },
    'x-ms-request-id' => {
      name: 'x-ms-request-id',
      value: '87e6b362-02d7-4b9b-87bb-a91e57601d52'
    },
    'x-ms-auth-token' => {
      name: 'x-ms-auth-token',
      value: 'Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IasdfasdfasdfggfhasdfasdfEREMkQxNzg2RTIwNTVBMkY5NjYxMUEiLCJ0eXAiOiJKV1QifQ.eyJwcm4iOksrebjzYkE9PSIsInN1YiI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vYmx1ZS13YXZlLTA3MjBkM2UwMC4zLmF6dXJlc3RhdGljYXBwcy5uZXQvLmF1dGgiLCJhdWQiOiJodHRwczovL2Z4YS1zaXRlbWFwNDAtZGV2LWVhcy5henVyZXdlYnNpdGVzLm5ldCIsIm5iZiI6MTY5Mjc0NDQ4NiwiZXhwIjoxNjkyNzQ0Nzg2LCJpYXQiOjE2OTI3NDQ0ODZ9.KYE8fpMxws_ZqEdTL1l459yjy8RQ0LlhKQJI9SdUJhaB2HnW9NLsBWJKkLzA2f66k0uMQ7YEabekFaoDAfyKDwvDkF2vSnTj7wGKaMM6BiOFTAs1tgTfrgz8IlwEfhzl9fcMA208oefNkDpvRyNufLeVGr_NKtlfEKRm1w7gIm24pD5fiuLkQIBvtDJov2ow7RPAXDkQa7V2Xf5CAcOlE27qz2WQVtEL76HEIFb6z07O2HZg8Q99SuLdna0QLpnn1pk85W2eVkJVTbGjjkf5k_t3K8Fy6e5uu9knDKNud5_prr4zJ3hLmUW7Gsxhruz8OY_DjiPfN02VV4a14NX5yQ'
    },
    'disguised-host' => {
      name: 'disguised-host',
      value: 'fxa-xxxxxx-dev-eas.azurewebsites.net'
    },
    'x-ms-client-principal-id' => { name: 'x-ms-client-principal-id', value: 'anonymous' },
    'x-ms-client-principal-idp' => { name: 'x-ms-client-principal-idp', value: 'azureStaticWebApps' }
  },
  [Symbol(headers map sorted)]: null
}

I see that it as an x-ms-auth-token that I suspect is what's being substituted for the x-functions-key somehow. I will start investigating that.


Solution

  • OK, I didn't think it was relevant to mention (but it is). I have the Functions attached to an Azure Static Web App (SWA). As indicated in this page's post:

    https://learn.microsoft.com/en-au/azure/static-web-apps/functions-bring-your-own

    See:

    enter image description here

    The authentication configuration by the SWA provides a redirect layer above the function app. This is modifying the header and exchanging it with the managed identities token in the x-ms-auth-token value.

    The information in the Function App's Authentication blade gave the hint on what to investigate further on:

    enter image description here

    As the instructions explain, you need to make the calls to the Function through the SWA to the API.