View Source AppIdentity.Plug (AppIdentity for Elixir v1.3.1)

A Plug that verifies App Identity proofs provided via one or more HTTP headers.

When multiple proof values are provided in the request, all must be successfully verified. If any of the proof values cannot be verified, request processing halts with 403 Forbidden. Should no proof headers be included, the request is considered invalid.

All of the above behaviours can be modified through configuration.

Checking Results

The results of AppIdentity.Plug are stored in Plug.Conn private storage as a map under the :app_identity key (this can be changed through the name option), keyed by the header group name. When using the headers option, each header is its own group. The header_groups option explicitly defines headers that will be treated as belonging to the same group.

Header results only appear in the result map if they are present in the request. If AppIdentity.Plug is configured for app1 and app2 headers, but there are only values in app1, the resulting output will not include app2.

Results Partitioning: Header Groups or Multiple Configurations?

AppIdentity.Plug provides two main ways to partition processed results: name or header groups (either automatic grouping via headers or explicit grouping via header_groups).

Most applications that require result partitioning will use header groups, because there's only one pool of applications defined. In this case, use the following configuration as a guide.

plug AppIdentity.Plug, finder: &MyApp.Application.get/1,
                       on_failure: :continue,
                       header_groups: %{
                          "application" => ["application-identity"],
                          "service" => ["service-identity"]
                       }

Later in request processsing, a controller or a route-specific Phoenix pipeline could call a require_application function which pulls from conn.private[:app_identity] with the appropriate header group name for verification.

If there are separate pools of applications defined, or there is a need to have different on_failure conditions, then configure two AppIdentity.Plugs with different names . The following example configuration would allow application-identity headers to fail without halting (even if omitted), but a missing or incorrect service-identity header would cause failures immediately.

plug AppIdentity.Plug, finder: &MyApp.Application.get/1,
                       on_failure: :continue,
                       headers: ["application-identity"]

plug AppIdentity.Plug, name: :service_app_identity,
                       finder: {MyApp.ServiceApplication, :get},
                       header: ["service-identity"]

Multiple Plugs Warning

If multiple AppIdentity.Plug configurations are used, different name values must be specified or the later plug will overwrite the results from the earlier plug.

Configuration

AppIdentity.Plug requires configuration for app discovery and identity headers and offers further configuration. Static configuration is strongly recommended.

App Discovery

So that AppIdentity.Plug can find apps used in identity validation, at least one of apps or finder must be supplied. If both are present, the apps configuration is consulted before calling the finder function.

  • apps: A list of AppIdentity.App.t/0 or AppIdentity.App.input/0 values to be used for proof validation. Duplicate apps will be ignored.

    plug AppIdentity.Plug, apps: [app1, app2], ...
  • finder: A callback function conforming to AppIdentity.App.finder/0 that loads an AppIdentity.App.input/0 from an external source given a parsed proof. This may also be specified as a {module, function} tuple.

    plug AppIdentity.Plug, finder: &ApplicationModel.get/1
    plug AppIdentity.Plug, finder: {ApplicationModel, :get}

    AppIdentity.Plug does not cache the results of the finder function. Any caching should be implemented in your application.

Identity Headers

AppIdentity.Plug does not have any default headers to search for app identity validation, requiring one of headers or header_groups to be configured. If both are present, an exception will be raised during configuration.

  • headers: A list of valid HTTP header names, which will be normalized on initialization.

    plug AppIdentity.Plug, headers: ["application-identity"], ...

    The result output uses each header name as the key for the related proof results. A configuration of headers: ["app1", "app2"] can produce a result map like %{"app1" => [...], "app2" => [...]}.

    Duplicate header names will result in an error. This option must be omitted if header_groups is used.

  • header_groups: A map of header group names to valid HTTP header names.

    When using header_groups, there is no guaranteed order for processing groups, but the each headers within a group will be processed in the order provided.

    plug AppIdentity.Plug,
      header_groups: %{
                        "application" => ["application", "my-application"],
                        "service" => ["service", "my-service"],
                        },
      ...

    The result output uses each header group name as the key for the related proof results from any header in that group. A configuration of header_groups: %{"app" => ["app1", "app2"], "svc" => ["svc1"]} can produce a result map like %{"app" => [...], "svc" => [...]}.

    Duplicate header names across any header groups will result in an error. This option must be omitted if headers is used.

headers or header_groups?

The correct choice between headers and header_groups depends on your application's requirements, but headers can be expressed as header_groups for ease of changing later.

That is, the following configurations are equivalent:

  plug AppIdentity.Plug, headers: ["application-identity"], ...
  plug AppIdentity.Plug,
    headers_groups: %{"application-identity" => ["application-identity"]}, ...

If your requirements treat each header uniquely, headers is a useful shorthand configuration.

Callbacks

There are three configuration options that can be implemented as callbacks: on_failure, on_success, and on_resolution.

  • on_failure: The behaviour of the AppIdentity.Plug when proof validation fails. If not provided, this defaults to :forbidden. When provided, it must be one of the following values:

    • :forbidden: Halt request processing and respond with a 403 (forbidden) status. This is the same as {:halt, :forbidden}.

      plug AppIdentity.Plug, on_failure: :forbidden
    • {:halt, Plug.Conn.status()}: Halt request processing and return the specified status code. An empty body is emitted.

      plug AppIdentity.Plug, on_failure: {:halt, :forbidden}
    • {:halt, Plug.Conn.status(), Plug.Conn.body()}: Halt request processing and return the specified status code. The body value is included in the response.

      plug AppIdentity.Plug, on_failure: {:halt, :forbidden, ["Evicted"]}
    • :continue: Continue processing, ensuring that failure states are recorded for the application to act on at a later point. This could be used to implement a distinction between validating a proof and requiring that the proof is valid.

      plug AppIdentity.Plug, on_failure: :continue
    • A 1-arity callback function (or a {module, function} tuple) that accepts a Plug.Conn and returns one of the above values. on_failure callbacks must not modify the passed conn value.

      plug AppIdentity.Plug, on_failure: {ApplicationModel, :resolve_proof_failure}
      plug AppIdentity.Plug, on_failure: &ApplicationModel.resolve_proof_failure/1
  • on_success: A 1-arity callback function (or a {module, function} tuple) that accepts a Plug.Conn when proof validation succeeds. The on_success callback may modify the passed conn value and must return the modified conn value.

  • on_resolution: An 1-arity callback function (or a {module, function} tuple) that accepts a Plug.Conn after proof validation completes, regardless of sucesss or failure. If present, this will be run as the last step (after on_failure and on_success). Because on_failure may halt pipeline processing, it may be necessary to check conn.halted. The on_resolution callback may modify the passed conn value and must return the modified conn value.

The on_success and on_resolution callbacks are optional.

Optional Configuration

  • name: An atom which will be used to store the AppIdentity.Plug results in Plug.Conn private storage. If not provided, defaults to :app_identity. Required if AppIdentity.Plug will be specified more than once as results are not merged.

    plug AppIdentity.Plug, name: :service_app, ...
  • disallowed: A list of algorithm versions that are not allowed when processing received identity proofs. See AppIdentity.disallowed/0.

    plug AppIdentity.Plug, disallowed: [1], ...

Telemetry

When telemetry is enabled, this plug will emit [:app_identity, :plug, :start] and [:app_identity, :plug, :stop] events.