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

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-appidentity-plug-results

Checking AppIdentity.Plug 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-name-or-header-groups

Results Partitioning: name or Header Groups?

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 coud 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/1,
                       header: ["service-identity"]

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.

telemetry

Telemetry

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

Link to this section Summary

Types

AppIdentity.Plug configuration options prior to validation.

t()

Normalized options for AppIdentity.Plug.

Link to this section Types

@type on_failure() ::
  :forbidden
  | :continue
  | {:halt, Plug.Conn.status()}
  | {:halt, Plug.Conn.status(), Plug.Conn.body()}
@type on_failure_fn() ::
  (Plug.Conn.t() -> on_failure()) | {module(), function :: atom()}
@type option() ::
  AppIdentity.disallowed()
  | {:headers, [binary()]}
  | {:header_groups, %{required(binary()) => [binary()]}}
  | {:apps, [AppIdentity.App.input() | AppIdentity.App.t()]}
  | {:finder, AppIdentity.App.finder()}
  | {:name, atom()}
  | {:on_failure, on_failure() | on_failure_fn()}

AppIdentity.Plug configuration options prior to validation.

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

    plug AppIdentity.Plug, apps: [app1, app2], ...
  • disallowed: A list of algorithm versions that are not allowed when processing received identity proofs. See AppIdentity.disallowed/0.

    plug AppIdentity.Plug, disallowed: [1], ...
  • finder: An AppIdentity.App.finder/0 function to load an AppIdentity.App.input/0 from an external source given a parsed proof.

    plug AppIdentity.Plug, finder: &ApplicationModel.get/1
  • headers: A list of 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 HTTP header names. Both header group names and HTTP header names must be binary strings.

    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.

  • 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, ...
  • on_failure: The behaviour of the AppIdentity.Plug when proof validation fails. 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}. This is the default on_failure behaviour.

      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 anonymous function or a {module, function} tuple accepting Plug.Conn that returns one of the above values.

      plug AppIdentity.Plug, on_failure: {ApplicationModel, :resolve_proof_failure}
      plug AppIdentity.Plug, on_failure: &ApplicationModel.resolve_proof_failure/1

At least one of apps or finder must be supplied. If both are present, apps are looked up in the apps list first.

Only one of headers or header_groups may be supplied. If both are present, an exception will be raised.

@type t() :: %AppIdentity.Plug{
  apps: %{optional(AppIdentity.id()) => AppIdentity.App.t()},
  disallowed: [AppIdentity.version()],
  finder: nil | AppIdentity.App.finder(),
  header_groups: nil | %{required(binary()) => binary()},
  headers: nil | [binary()],
  name: atom(),
  on_failure: on_failure() | {:fn, on_failure()}
}

Normalized options for AppIdentity.Plug.