Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error with multitenant Azure login #37

Closed
gregoribic opened this issue Apr 15, 2020 · 5 comments
Closed

Error with multitenant Azure login #37

gregoribic opened this issue Apr 15, 2020 · 5 comments

Comments

@gregoribic
Copy link

[error] #PID<0.888.0> running TaskAppWeb.Endpoint (connection #PID<0.828.0>, stream id 27) terminated
Server: localhost:4006 (https)
Request: POST /auth/azure/callback
** (exit) an exception was raised:
** (RuntimeError) Invalid issuer "https://login.microsoftonline.com/270a4662-e407-4044-b299-1a62945d3893/v2.0" in ID Token
(pow_assent 0.4.6) lib/pow_assent/phoenix/controllers/authorization_controller.ex:209: PowAssent.Phoenix.AuthorizationController.handle_strategy_error/1
(pow_assent 0.4.6) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.action/2
(pow_assent 0.4.6) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.phoenix_controller_pipeline/2
(phoenix 1.4.16) lib/phoenix/router.ex:288: Phoenix.Router.call/2
(task_app 0.1.0) lib/task_app_web/endpoint.ex:1: TaskAppWeb.Endpoint.plug_builder_call/2
(task_app 0.1.0) lib/plug/debugger.ex:132: TaskAppWeb.Endpoint."call (overridable 3)"/2
(task_app 0.1.0) lib/task_app_web/endpoint.ex:1: TaskAppWeb.Endpoint.call/2
(phoenix 1.4.16) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4

@danschultzer
Copy link
Collaborator

Thanks, sorry for the delay, been super busy with work.

So continuing from the elixirforum this is due to how OpenID specs works. There are some options you can try out.

Use OAuth2 base strategy

Instead of depending on OpenID, you could use the OAuth 2.0 base strategy with like this (same as what the ueberauth strategy does):

[
  client_id: "REPLACE_WITH_CLIENT_ID",
  client_secret: "REPLACE_WITH_CLIENT_SECRET",
  auth_method: :client_secret_post,
  site: "https://graph.microsoft.com",
  authorize_url: "https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/authorize",
  token_url: "https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/token",
  authorization_params: [scope: "https://graph.microsoft.com/user.read openid email offline_access"],
  user_url: "https://graph.microsoft.com/v1.0/me/"
]

Not sure if this works out of the box, but it would be pretty easy to set it up as a custom strategy to parse the right user struct.

Dynamically set the tenant id

You may be able to continue using OIDC if you can fetch the tenant id from the returned auth token. This would require you to add a plug or similar, and then dynamically update the config like this: pow-auth/pow_assent#117 (comment)

@danschultzer
Copy link
Collaborator

I assume this has been resolved 🙂

@gregoribic
Copy link
Author

I did not managed to get it working without tenant id.

@danschultzer
Copy link
Collaborator

With the new v0.1.12 release of assent, you will be able to customize the get_user callback. You can either bypass the validation entirely or dynamically update the issuer in the config.

The latter could look something like this (untested):

defmodule MyAppWeb.AzureADStrategy do
  @moduledoc false
  use Assent.Strategy.OIDC.Base

  alias Assent.{Config, Strategy.OIDC}

  @impl true
  def default_config(config) do
    [
      site: "https://login.microsoftonline.com/common/v2.0",
      authorization_params: [scope: "email profile", response_mode: "form_post"],
      client_auth_method: :client_secret_post,
    ]
  end

  @impl true
  def normalize(_config, user), do: {:ok, user}
  
  @impl true
  def get_user(config, token) do
    with {:ok, issuer} <- fetch_iss(token["id_token"], config),
         {:ok, config} <- update_issuer_in_config(config, issuer),
         {:ok, jwt}    <- OIDC.validate_id_token(config, token["id_token"]) do
      Helpers.normalize_userinfo(jwt.claims)
    end
  end
  
  defp fetch_iss(encoded, config) do
    with [_, encoded, _] <- String.split(encoded, "."),
         {:ok, json}     <- Base.url_decode64(encoded, padding: false),
         {:ok, claims}   <- Config.json_library(config).decode(json) do
      Map.fetch(claims, "iss")
    else
      {:error, error} -> {:error, error}
      _any            -> {:error, "The ID Token is not a valid JWT"}
    end
  end
  
  defp update_issuer_in_config(config, issuer) do
    openid_configuration = Map.put(config[:openid_configuration], "issuer", issuer)
    
    {:ok, Map.put(config, :openid_configuration, openid_configuration)}
  end
end

@vince-roy
Copy link

vince-roy commented Mar 22, 2021

In case it might help anyone, this version based on what Dan kindly provided above worked for me:

defmodule MyApp.Assent.AzureADCommonStrategy do
  @moduledoc false
  use Assent.Strategy.OIDC.Base

  alias Assent.{Config, Strategy.OIDC}

  @impl true
  def default_config(config) do
    [
      authorization_params: [scope: "email profile", response_mode: "form_post"],
      client_auth_method: :client_secret_post,
      site: "https://login.microsoftonline.com/common/v2.0"
    ]
  end

  @impl true
  def normalize(_config, user), do: {:ok, user}

  @impl true
  def fetch_user(config, token) do
    with {:ok, issuer} <- fetch_iss(token["id_token"], config),
         {:ok, config} <- update_issuer_in_config(config, issuer),
         {:ok, jwt}    <- OIDC.validate_id_token(config, token["id_token"]) do
      Helpers.normalize_userinfo(jwt.claims)
    end
  end

  defp fetch_iss(encoded, config) do
    with [_, encoded, _] <- String.split(encoded, "."),
         {:ok, json}     <- Base.url_decode64(encoded, padding: false),
         {:ok, claims}   <- Config.json_library(config).decode(json) do
      Map.fetch(claims, "iss")
    else
      {:error, error} -> {:error, error}
      _any            -> {:error, "The ID Token is not a valid JWT"}
    end
  end

  defp update_issuer_in_config(config, issuer) do
    openid_configuration = Map.put(config[:openid_configuration], "issuer", issuer)
    {:ok, Keyword.put(config, :openid_configuration, openid_configuration)}
  end
end

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

No branches or pull requests

3 participants