Closed gregoribic closed 4 years ago
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.
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.
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: https://github.com/pow-auth/pow_assent/issues/117#issuecomment-571837184
I assume this has been resolved 🙂
I did not managed to get it working without tenant id.
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
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
[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