himanago

Azure・C#などのMS系技術やLINE関連技術など、好きな技術について書くブログ

Azure Functions の HTTP トリガーで、キーが間違っていないのに 401 エラーとなってしまうケース

前置き

Azure Functions の HTTP トリガーの関数では、API キーを関数 URL のクエリストリング(code)に入れて実行しますね(承認レベルが匿名の場合は不要)。

https://example.azurewebsites.net/api/Function1?code=xxxxxxxx

この API キーを間違えたり、渡さなかったりすると 401 エラーが出るわけですが、今回はこのキーを間違えていないはずなのに 401 エラーが出てしまうケースを紹介します。

401 エラーになるコード

以下のような関数のコードを見てください。

承認レベルは Function で、関数ごとに異なる キーを渡して実行します。

これをデプロイして Function2 を実行すると、なんと正しい API キーを渡しても 401 エラーになります

public static class SampleFunctions
{
    [FunctionName(nameof(Function1))]
    public static IActionResult Function1(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = "{name}")] HttpRequest req,
        string name)
    {
        return new OkObjectResult($"Function1: {name}");
    }

    [FunctionName(nameof(Function2))]
    public static IActionResult Function2(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req)
    {
        return new OkObjectResult("Function2: OK");
    }
}




さて、なぜだかわかりますか???




解説

ポイントは各関数メソッドの Route = の部分です。

Route でパラメータを渡せる

Route では、関数を呼び出す URL の 「https://example.azurewebsites.net/api/」以降のパスを指定することができます。

そして、{ } でパラメータも渡すことができ、このパラメータの値は同じ名前の別の引数としてメソッド内部で利用可能です。

したがって Function1 のこの部分、

public static IActionResult Function1(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = "{name}")] HttpRequest req,
        string name)

関数 URL としては

https://example.azurewebsites.net/api/JohnDoe?code=xxxxxxxx

のようになり、好きな文字列を渡して関数内で使うことができるようになります。

Route = null

ところが、Function2 では、Route = null となっています。

Routenull の場合は、URL のパスに関数名が入ります。つまり、こんな URL になります。

https://example.azurewebsites.net/api/Function2?code=XXXXXXXX



もうおわかりですね

そう、

  • Function1 の指定 Route = {name}
  • Function2 の指定 Route = null

が、URL として完全にかぶってしまっているのです。

そして、定義として先にくる Function1 が優先され、「https://example.azurewebsites.net/api/{何かしらの文字列}」という Function1 の URL に当てはまるhttps://example.azurewebsites.net/api/Function2」という Function2 の URL も、Function1 の呼び出しだと思われてしまうため、Function2 のキーを渡しても Function1 としては間違ったキーとなるので、401 エラーになってしまう、ということになります。


ちなみに

もし Function1Function2 の名前が逆だったら、つまり

  • Function1 の指定 Route = null
  • Function2 の指定 Route = {name}

という定義であれば、/Function1 という呼び出しは定義が先の Function1 と扱ってくれるため、どちらもエラーにならず 200 が返ってきます。

しかし、この場合でも Function2 の name に「Function1」という文字列を渡そうとしたら……?

恐ろしいですね。

そんなことが起きないように

ということで、Route を指定する場合は必ず固定のパスを含んだかたちで指定するようにしましょう。

ドキュメントのサンプルを見てもきっちりここは指定されているはずです。

[FunctionName(nameof(Function1))]
public static IActionResult Function1(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Function1/{name}")] HttpRequest req,
    string name)
{
    return new OkObjectResult($"Function1: {name}");
}

固定のパスを指定しなくても、そしてたとえ URL がかぶっていたとしてもデプロイや起動時にエラーになってくれないので、注意が必要です。