Closed leifericf closed 5 years ago
I would appreciate that! I'm not familiar with Azure AD, but looks like it can be done using the OAuth 2.0 protocol: https://github.com/KonaTeam/omniauth-azure-oauth2
If you just need to use OAuth 2.0, then it'll take very little time to implement as PowAssent already have that strategy built-in. Take a look at the Google OAuth2 integration:
https://github.com/danschultzer/pow_assent/blob/master/lib/pow_assent/strategies/google.ex https://github.com/danschultzer/pow_assent/blob/master/test/pow_assent/strategies/google_test.exs
You'll mostly just need to update URL's, and add the right user profile API response for testing that the information can be normalized to something useful for PowAssent.
From this guide, I see that a JWT can be used instead of a client secret for added security. That would require a bit more code in the callback phase similar to how the facebook integration modifies the callback flow to add in app proof: https://github.com/danschultzer/pow_assent/blob/master/lib/pow_assent/strategies/facebook.ex#L19-L34
I would be happy to help out making the integration work If you create a PR. I can work on the parts that requires some more code like the certificate and response handling (from what I can read it seems that also the API response will be encoded as a JWT).
Thanks a bunch for the pointers and offering to help! I think you're right about being able to use OAuth 2.0. I'll look into that first. Unfortunately, the concepts of authentication and authorisation (except simple username/password setups) are quite foreign to me still, so it will take some time.
I've only tested used the provided information in the Azure AD OAuth 2.0 tutorial, but this hopefully works: https://github.com/danschultzer/pow_assent/pull/3
You can read about tenant id configuration here: https://github.com/danschultzer/pow_assent/blob/bbb500d7134b50a369226c663953932703767a4d/lib/pow_assent/strategies/azure_oauth2.ex#L2-L30
I don't have an azure account myself, so I'll need to set up one later to test it when I got some free time.
Awesome, thanks! I'm still struggling with more basics of Elixir and Phoenix at the moment, but I'll definitely give this a whirl at the office.
I've tried this out today. Think I've got PowAssent configured correctly for Azure AD.
Gave this a whirl today and encountered this error after setting up:
[error] #PID<0.429.0> running AzureAuthWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /registration/new
** (exit) an exception was raised:
** (ArgumentError) unknown field `email`. Only fields, embeds and associations (except :through ones) are supported in changesets
(ecto) lib/ecto/changeset.ex:489: Ecto.Changeset.type!/2
(ecto) lib/ecto/changeset.ex:464: Ecto.Changeset.process_param/7
(elixir) lib/enum.ex:1925: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto) lib/ecto/changeset.ex:449: Ecto.Changeset.cast/6
(pow) lib/pow/ecto/schema/changeset.ex:43: Pow.Ecto.Schema.Changeset.user_id_field_changeset/3
(azure_auth) lib/azure_auth/users/user.ex:3: AzureAuth.Users.User.pow_changeset/2
(pow) lib/pow/phoenix/controllers/registration_controller.ex:15: Pow.Phoenix.RegistrationController.process_new/2
(pow) lib/pow/phoenix/controllers/controller.ex:87: Pow.Phoenix.Controller.action/3
(pow) lib/pow/phoenix/controllers/registration_controller.ex:1: Pow.Phoenix.RegistrationController.action/2
(pow) lib/pow/phoenix/controllers/registration_controller.ex:1: Pow.Phoenix.RegistrationController.phoenix_controller_pipeline/2
(azure_auth) lib/azure_auth_web/endpoint.ex:1: AzureAuthWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(azure_auth) lib/azure_auth_web/endpoint.ex:1: AzureAuthWeb.Endpoint.plug_builder_call/2
(azure_auth) lib/plug/debugger.ex:122: AzureAuthWeb.Endpoint."call (overridable 3)"/2
(azure_auth) lib/azure_auth_web/endpoint.ex:1: AzureAuthWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) /Users/leif/Code/azure_auth/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
Currently trying to figure out why that error occurs.
Can you post the content of your user.ex
module? It must be missing the email
field.
Oh, and the migration file for your user table.
My user.ex
looks like this:
defmodule AzureAuth.Users.User do
use Ecto.Schema
use Pow.Ecto.Schema
use PowAssent.Ecto.Schema
schema "users" do
has_many :user_identities,
AzureAuth.UserIdentities.UserIdentity,
on_delete: :delete_all
end
end
And the migration file for the user table looks like this (as it was generated by Pow):
defmodule AzureAuth.Repo.Migrations.CreateUsers do
use Ecto.Migration
def change do
create table(:users) do
add :email, :string, null: false
add :password_hash, :string
timestamps()
end
create unique_index(:users, [:email])
end
end
Update your user schema to this:
defmodule AzureAuth.Users.User do
use Ecto.Schema
use Pow.Ecto.Schema
use PowAssent.Ecto.Schema
schema "users" do
has_many :user_identities,
AzureAuth.UserIdentities.UserIdentity,
on_delete: :delete_all
pow_user_fields()
timestamps()
end
end
Aha! Of course, silly me. I removed pow_user_fields()
and timestamps()
.
Now it works!
Great, I've updated the readme to make it clear that those two fields should stay 😄
Hmmm. Encountering a different error when visiting http://localhost:4000/auth/azure/new/
:
[error] #PID<0.525.0> running AzureAuthWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /auth/azure/new/
** (exit) an exception was raised:
** (UndefinedFunctionError) function PowAssent.Strategy.AzureOAuth2.authorize_url/2 is undefined (module PowAssent.Strategy.AzureOAuth2 is not available)
PowAssent.Strategy.AzureOAuth2.authorize_url([redirect_uri: "http://localhost:4000/auth/azure/callback", client_id: "my_client_id", client_secret: "my_client_secret", strategy: PowAssent.Strategy.AzureOAuth2], %Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{callback_url: "http://localhost:4000/auth/azure/callback", current_user: nil}, before_send: [#Function<0.116269836/1 in Plug.CSRFProtection.call/2>, #Function<4.101542213/1 in Phoenix.Controller.fetch_flash/2>, #Function<0.58261320/1 in Plug.Session.before_send/2>, #Function<1.25166163/1 in Plug.Logger.call/2>, #Function<0.61641163/1 in Phoenix.LiveReloader.before_send_inject_reloader/2>], body_params: %{}, cookies: %{"_azure_auth_key" => "my_azure_auth_key"}, halted: false, host: "localhost", method: "GET", owner: #PID<0.525.0>, params: %{"provider" => "azure"}, path_info: ["auth", "azure", "new"], path_params: %{"provider" => "azure"}, peer: {{127, 0, 0, 1}, 57299}, port: 4000, private: %{AzureAuthWeb.Router => {[], %{}}, :phoenix_action => :new, :phoenix_controller => PowAssent.Phoenix.AuthorizationController, :phoenix_endpoint => AzureAuthWeb.Endpoint, :phoenix_flash => %{}, :phoenix_format => "html", :phoenix_layout => {AzureAuthWeb.LayoutView, :app}, :phoenix_pipelines => [:browser], :phoenix_router => AzureAuthWeb.Router, :phoenix_view => PowAssent.Phoenix.AuthorizationView, :plug_session => %{"_csrf_token" => "my_token"}, :plug_session_fetch => :done, :pow_config => [mod: Pow.Plug.Session, otp_app: :azure_auth]}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %{"_azure_auth_key" => "my_azure_auth_key"}, req_headers: [{"host", "localhost:4000"}, {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}, {"upgrade-insecure-requests", "1"}, {"cookie", "_azure_auth_key=my_azure_auth_key"}, {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.2 Safari/605.1.15"}, {"accept-language", "en-us"}, {"accept-encoding", "gzip, deflate"}, {"connection", "keep-alive"}], request_path: "/auth/azure/new/", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}, {"x-frame-options", "SAMEORIGIN"}, {"x-xss-protection", "1; mode=block"}, {"x-content-type-options", "nosniff"}, {"x-download-options", "noopen"}, {"x-permitted-cross-domain-policies", "none"}], scheme: :http, script_name: [], secret_key_base: "my_secret_key_base", state: :unset, status: nil})
(pow_assent) lib/pow_assent/plug.ex:20: PowAssent.Plug.authenticate/3
(pow) lib/pow/phoenix/controllers/controller.ex:87: Pow.Phoenix.Controller.action/3
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.action/2
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.phoenix_controller_pipeline/2
(azure_auth) lib/azure_auth_web/endpoint.ex:1: AzureAuthWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(azure_auth) lib/azure_auth_web/endpoint.ex:1: AzureAuthWeb.Endpoint.plug_builder_call/2
(azure_auth) lib/plug/debugger.ex:122: AzureAuthWeb.Endpoint."call (overridable 3)"/2
(azure_auth) lib/azure_auth_web/endpoint.ex:1: AzureAuthWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) /Users/leif/Code/azure_auth/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
You'll have to use the azure oauth2 github branch in your mix dependencies:
{:pow_assent, git: "https://github.com/danschultzer/pow_assent.git", branch: "azure-oauth2-strategy"}
I'll merge it in once I can confirm it works.
Aha! I see. Switched branch and got one step further, now this error is thrown:
[info] GET /auth/azure/new/
[debug] Processing with PowAssent.Phoenix.AuthorizationController.new/2
Parameters: %{"provider" => "azure"}
Pipelines: [:browser]
[info] Sent 500 in 74ms
[error] #PID<0.3887.0> running AzureAuthWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /auth/azure/new/
** (exit) an exception was raised:
** (FunctionClauseError) no function clause matching in Keyword.get/3
(elixir) lib/keyword.ex:188: Keyword.get(%OAuth2.Client{authorize_url: "/common/oauth2/authorize", client_id: "my_client_id", client_secret: "my_client_secret", headers: [], params: %{}, redirect_uri: "http://localhost:4000/auth/azure/callback", ref: nil, request_opts: [], site: "https://login.microsoftonline.com", strategy: PowAssent.Strategy.AzureOAuth2, token: nil, token_method: :post, token_url: "/common/oauth2/token"}, :tenant_id, "common")
(pow_assent) lib/pow_assent/strategies/azure_oauth2.ex:46: PowAssent.Strategy.AzureOAuth2.default_config/1
(pow_assent) lib/pow_assent/strategies/azure_oauth2.ex:42: PowAssent.Strategy.AzureOAuth2.set_config/1
(pow_assent) lib/pow_assent/strategies/azure_oauth2.ex:42: PowAssent.Strategy.AzureOAuth2.authorize_url/2
(oauth2) lib/oauth2/client.ex:188: OAuth2.Client.authorize_url/2
(oauth2) lib/oauth2/client.ex:201: OAuth2.Client.authorize_url!/2
(pow_assent) lib/pow_assent/strategies/oauth2.ex:36: PowAssent.Strategy.OAuth2.authorize_url/2
(pow_assent) lib/pow_assent/plug.ex:20: PowAssent.Plug.authenticate/3
(pow) lib/pow/phoenix/controllers/controller.ex:87: Pow.Phoenix.Controller.action/3
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.action/2
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.phoenix_controller_pipeline/2
(azure_auth) lib/azure_auth_web/endpoint.ex:1: AzureAuthWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(azure_auth) lib/azure_auth_web/endpoint.ex:1: AzureAuthWeb.Endpoint.plug_builder_call/2
(azure_auth) lib/plug/debugger.ex:122: AzureAuthWeb.Endpoint."call (overridable 3)"/2
(azure_auth) lib/azure_auth_web/endpoint.ex:1: AzureAuthWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) /Users/leif/Code/azure_auth/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
Sorry, I introduced an error in the last commits. Try run mix deps.update pow_assent
to update PowAssent, and see if it doesn't work now!
Nice! One step further. I feel like we're getting close!
Now I get redirected to Microsoft's page, and it shows this error:
Request Id: my_request_id
Correlation Id: my_correlation_id
Timestamp: 2018-09-10T09:56:24Z
Message: AADSTS50011: The reply url specified in the request does not match the reply urls configured for the application: 'my_application_id'.
I suspect this is because the callback URL is not correctly set in Azure AD on my end.
I have asked our resident Azure AD expert to set this callback URL for testing:
http://localhost:4000/auth/azure/callback
Will continue testing once that is done 👍
I think we've managed to fix the callback URL, and now it seems like I'm getting some kind of response. But now it's complaining about Cross-Site Request Forgery (CSRF):
[info] GET /auth/azure/new/
[debug] Processing with PowAssent.Phoenix.AuthorizationController.new/2
Parameters: %{"provider" => "azure"}
Pipelines: [:browser]
[info] Sent 302 in 63ms
[info] GET /auth/azure/callback
[debug] Processing with PowAssent.Phoenix.AuthorizationController.callback/2
Parameters: %{"code" => "LONG_CODE", "provider" => "azure", "session_state" => "SESSION_STATE", "state" => "STATE"}
Pipelines: [:browser]
[info] Sent 500 in 828ms
[error] #PID<0.428.0> running MyAppWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /auth/azure/callback?code=LONG_CODE&state=STATE&session_state=SESSION_STATE
** (exit) an exception was raised:
** (PowAssent.RequestError) invalid_resource
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:113: PowAssent.Phoenix.AuthorizationController.handle_strategy_error/1
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.action/2
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.phoenix_controller_pipeline/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
(my_app) lib/plug/debugger.ex:122: MyAppWeb.Endpoint."call (overridable 3)"/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) c:/Code/my_app/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
[info] GET /auth/azure/callback
[debug] Processing with PowAssent.Phoenix.AuthorizationController.callback/2
Parameters: %{"code" => "OTHER_LONG_CODE", "provider" => "azure", "session_state" => "SESSION_STATE", "state" => "STATE"}
Pipelines: [:browser]
[info] Sent 500 in 62ms
[error] #PID<0.429.0> running MyAppWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /auth/azure/callback?code=OTHER_LONG_CODE&state=STATE&session_state=SESSION_STATE
** (exit) an exception was raised:
** (PowAssent.CallbackCSRFError) CSRF detected
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:113: PowAssent.Phoenix.AuthorizationController.handle_strategy_error/1
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.action/2
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.phoenix_controller_pipeline/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
(my_app) lib/plug/debugger.ex:122: MyAppWeb.Endpoint."call (overridable 3)"/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) c:/Code/my_app/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
Note that I have removed the insanely long string codes/tokens from the call stack and replaced them with all caps strings instead (one per unique code/token). Just to make it easier to read.
Comment from our resident Azure AD guy when seeing the call stack:
What you can see now is that you have received a "code" back to your solution. This code has "lifetime" of 5 minuets, now you have to use this code and ask for the id_token for Azure AD. But this should be handled by your provider.
The CSFR happens because state=STATE
differs from what is saved in your session. You should first just restart the app, and try again. It may just have been that you were testing with an old state.
Hmmm. I've tried restarting the app a couple of times, but the issue persists. I suspect that maybe it has something to do with our Azure AD setup, parts of which might be on-premise. Maybe I'll need to use some different site than https://login.microsoftonline.com
in my strategy config.
With this error you can be pretty sure that things are working on the Azure side of things. It's PowAssent that can't validate the request with the state. Do you have a standard Phoenix setup? The state is saved in the session. The state is set in the query when the user is redirected to the Azure login page, and that state is returned in the redirect uri query after sign in.
Hmm, I've tested locally with Azure, and works as expected. Not sure why it doesn't validate the state for you.
Is the state param same in the URI when you are redirect to azure, as when azure redirects you to the app? If it is, then the issue is with the session (either the value is being overridden, or the session is not persisted between requests for some reason).
I went back and checked the error message from yesterday. I ca see that the session_state
and session
parameters were identical in the request and response, but the code
parameter differed.
I did some more testing this morning, and it seems like the CSFR only occurs on subsequent attempts, after waiting for a couple of minutes. Below is a more detailed log with several attempts in two browsers tabs (without restarting the app) where the parameters are not removed (but abbreviated):
C:\Code\my_app>mix phx.server
[warn] Phoenix is unable to create symlinks. Phoenix' code reloader will run considerably faster if symlinks are allowed. On Windows, the lack of symlinks may even cause empty assets to be served. Luckily, you can address this issue by starting your Windows terminal at least once with "Run as Administrator" and then running your Phoenix application.
[info] Running MyAppWeb.Endpoint with Cowboy using http://0.0.0.0:4000
09:37:08 - [32minfo[39m: compiled 6 files into 2 files, copied 3 in 1.2 sec
--------------------------------
First attempt in browser tab #1:
--------------------------------
[info] GET /auth/azure/new/
[debug] Processing with PowAssent.Phoenix.AuthorizationController.new/2
Parameters: %{"provider" => "azure"}
Pipelines: [:browser]
[info] Sent 302 in 31ms
[info] GET /auth/azure/callback
[debug] Processing with PowAssent.Phoenix.AuthorizationController.callback/2
Parameters: %{"code" => "AQABAA...ja0gAA", "provider" => "azure", "session_state" => "dac33c...2a785f", "state" => "b5a2b2...fe13cb"}
Pipelines: [:browser]
[info] Sent 500 in 516ms
[error] #PID<0.429.0> running MyAppWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /auth/azure/callback?code=AQABAA...ja0gAA&state=b5a2b2...fe13cb&session_state=dac33c...2a785f
** (exit) an exception was raised:
** (PowAssent.RequestError) invalid_resource
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:113: PowAssent.Phoenix.AuthorizationController.handle_strategy_error/1
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.action/2
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.phoenix_controller_pipeline/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
(my_app) lib/plug/debugger.ex:122: MyAppWeb.Endpoint."call (overridable 3)"/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) c:/Code/my_app/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
-----------------------------------------------------------
Closed browser tab #1. First attempt in new browser tab #2:
-----------------------------------------------------------
[info] GET /auth/azure/new/
[debug] Processing with PowAssent.Phoenix.AuthorizationController.new/2
Parameters: %{"provider" => "azure"}
Pipelines: [:browser]
[info] Sent 302 in 0┬╡s
[info] GET /auth/azure/callback
[debug] Processing with PowAssent.Phoenix.AuthorizationController.callback/2
Parameters: %{"code" => "AQABAA...NXQgAA", "provider" => "azure", "session_state" => "dac33c...2a785f", "state" => "52202c...dad628"}
Pipelines: [:browser]
[info] Sent 500 in 266ms
[error] #PID<0.439.0> running MyAppWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /auth/azure/callback?code=AQABAA...NXQgAA&state=52202c...dad628&session_state=dac33c...2a785f
** (exit) an exception was raised:
** (PowAssent.RequestError) invalid_resource
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:113: PowAssent.Phoenix.AuthorizationController.handle_strategy_error/1
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.action/2
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.phoenix_controller_pipeline/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
(my_app) lib/plug/debugger.ex:122: MyAppWeb.Endpoint."call (overridable 3)"/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) c:/Code/my_app/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
---------------------------------
Second attempt in browser tab #2:
---------------------------------
[info] GET /auth/azure/new/
[debug] Processing with PowAssent.Phoenix.AuthorizationController.new/2
Parameters: %{"provider" => "azure"}
Pipelines: [:browser]
[info] Sent 302 in 0┬╡s
[info] GET /auth/azure/callback
[debug] Processing with PowAssent.Phoenix.AuthorizationController.callback/2
Parameters: %{"code" => "AQABAA...mKAgAA", "provider" => "azure", "session_state" => "dac33c...2a785f", "state" => "a6efd6...621ae3"}
Pipelines: [:browser]
[info] Sent 500 in 250ms
[error] #PID<0.447.0> running MyAppWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /auth/azure/callback?code=AQABAA...mKAgAA&state=a6efd6...621ae3&session_state=dac33c...2a785f
** (exit) an exception was raised:
** (PowAssent.RequestError) invalid_resource
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:113: PowAssent.Phoenix.AuthorizationController.handle_strategy_error/1
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.action/2
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.phoenix_controller_pipeline/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
(my_app) lib/plug/debugger.ex:122: MyAppWeb.Endpoint."call (overridable 3)"/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) c:/Code/my_app/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
---------------------------------------------------------------------------
Third attempt in browser tab #2, after waiting a few minutes (CSRF occurs):
---------------------------------------------------------------------------
[info] GET /auth/azure/new/
[debug] Processing with PowAssent.Phoenix.AuthorizationController.new/2
Parameters: %{"provider" => "azure"}
Pipelines: [:browser]
[info] Sent 302 in 0┬╡s
[info] GET /auth/azure/callback
[debug] Processing with PowAssent.Phoenix.AuthorizationController.callback/2
Parameters: %{"code" => "AQABAA...APkgAA", "provider" => "azure", "session_state" => "dac33c...2a785f", "state" => "6244a2...f2e923"}
Pipelines: [:browser]
[info] Sent 500 in 250ms
[error] #PID<0.456.0> running MyAppWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /auth/azure/callback?code=AQABAA...APkgAA&state=6244a2...f2e923&session_state=dac33c...2a785f
** (exit) an exception was raised:
** (PowAssent.RequestError) invalid_resource
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:113: PowAssent.Phoenix.AuthorizationController.handle_strategy_error/1
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.action/2
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.phoenix_controller_pipeline/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
(my_app) lib/plug/debugger.ex:122: MyAppWeb.Endpoint."call (overridable 3)"/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) c:/Code/my_app/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
[info] GET /auth/azure/callback
[debug] Processing with PowAssent.Phoenix.AuthorizationController.callback/2
Parameters: %{"code" => "AQABAA...UmUgAA", "provider" => "azure", "session_state" => "dac33c...2a785f", "state" => "6244a2...f2e923"}
Pipelines: [:browser]
[info] Sent 500 in 0┬╡s
[error] #PID<0.457.0> running MyAppWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /auth/azure/callback?code=AQABAA...UmUgAA&state=6244a2...f2e923&session_state=dac33c...2a785f
** (exit) an exception was raised:
** (PowAssent.CallbackCSRFError) CSRF detected
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:113: PowAssent.Phoenix.AuthorizationController.handle_strategy_error/1
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.action/2
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.phoenix_controller_pipeline/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
(my_app) lib/plug/debugger.ex:122: MyAppWeb.Endpoint."call (overridable 3)"/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) c:/Code/my_app/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
---------------------------------
Fourth attempt in browser tab #2:
---------------------------------
[info] GET /auth/azure/new/
[debug] Processing with PowAssent.Phoenix.AuthorizationController.new/2
Parameters: %{"provider" => "azure"}
Pipelines: [:browser]
[info] Sent 302 in 0┬╡s
[info] GET /auth/azure/callback
[debug] Processing with PowAssent.Phoenix.AuthorizationController.callback/2
Parameters: %{"code" => "AQABAA...EckgAA", "provider" => "azure", "session_state" => "dac33c...2a785f", "state" => "3f2ada...91a963"}
Pipelines: [:browser]
[info] Sent 500 in 282ms
[error] #PID<0.466.0> running MyAppWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /auth/azure/callback?code=AQABAA...EckgAA&state=3f2ada...91a963&session_state=dac33c...2a785f
** (exit) an exception was raised:
** (PowAssent.RequestError) invalid_resource
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:113: PowAssent.Phoenix.AuthorizationController.handle_strategy_error/1
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.action/2
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.phoenix_controller_pipeline/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
(my_app) lib/plug/debugger.ex:122: MyAppWeb.Endpoint."call (overridable 3)"/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) c:/Code/my_app/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
Ah, seems like you get invalid_resource
error.
I've set up the following in my configuration:
azure: [
client_id: "CLIENT_ID"
client_secret: "CLIENT_SECRET",
tenant_id: "TENANT_ID",
authorization_params: [response_mode: "query", response_type: "code", resource: "https://graph.microsoft.com"],
strategy: PowAssent.Strategy.AzureOAuth2
]
I don't know enough about how azure works to understand the tenant/resource configuration. I just used https://graph.microsoft.com/
for resource.
Here's the description for the resource param:
The App ID URI of the target web API (secured resource). To find the App ID URI, in the Azure Portal, click Azure Active Directory, click Application registrations, open the application's Settings page, then click Properties. It may also be an external resource like https://graph.microsoft.com. This is required in one of either the authorization or token requests. To ensure fewer authentication prompts place it in the authorization request to ensure consent is received from the user.
And the tenant id:
The {tenant} value in the path of the request can be used to control who can sign into the application. The allowed values are tenant identifiers, for example, 8eaef023-2b34-4da1-9baa-8bc8c9d6a490 or contoso.onmicrosoft.com or common for tenant-independent tokens
I've an idea that it works this way because the resource (user) can be pulled from data you manage on azure or from your own platform (the OAuth2 guide shows an example with http://service.contoso.com/
), rather than from the actual Microsoft account users.
Hmm, from what I read it seems like the resource
param is mandatory. Maybe it'll be easiest for developers, if I add a default for resource
with https://graph.microsoft.com
. I assume this will pull their MS account details, and this is what most would need.
resource parameter is part of v1 endpoint, its not purre Oauth 2.0 specs, but this shoudl be defined as parameter so one can define for which resource the client would acuquire tokens for
Thanks for explaining that @belaie. I've added a resource
parameter that can be overridden but defaults to https://graph.microsoft.com
. The configuration can look like this (based on the values given in the OAuth2 guide from the Azure website):
azure: [
client_id: "REPLACE_WITH_CLIENT_ID",
client_secret: "REPLACE_WITH_CLIENT_SECRET",
tenant_id: "8eaef023-2b34-4da1-9baa-8bc8c9d6a490",
resource: "https://service.contoso.com/",
strategy: PowAssent.Strategy.AzureOAuth2
]
Hopefully everything will work now @IRLeif!
Very cool! Got it working now.
Note that I needed to pass the client_id
as the resource
, instead of the app ID URI. I'm not sure why exactly, but more details about this can be found in this answer on Stack Overflow.
When I used the app ID URI as the resource
, I got this error:
[info] GET /auth/azure/new/
[debug] Processing with PowAssent.Phoenix.AuthorizationController.new/2
Parameters: %{"provider" => "azure"}
Pipelines: [:browser]
[info] Sent 302 in 0┬╡s
[info] GET /auth/azure/callback
[debug] Processing with PowAssent.Phoenix.AuthorizationController.callback/2
Parameters: %{"error" => "invalid_request", "error_description" => "AADSTS90009: Application 'e8b...' is requesting a token for itself. This scenario is supported only if resource is specified using the GUID based App Identifier.\r\nTrace ID: 674...\r\nCorrelation ID: d43...\r\nTimestamp: 2018-09-14 08:23:55Z", "provider" => "azure", "state" => "33f..."}
Pipelines: [:browser]
[info] Sent 500 in 16ms
[error] #PID<0.564.0> running MyAppWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /auth/azure/callback?error=invalid_request&error_description=AADSTS90009%3a+Application+%27e8b...%27+is+requesting+a+token+for+itself.+This+scenario+is+supported+only+if+resource+is+specified+using+the+GUID+based+App+Identifier.%0d%0aTrace+ID%3a+674...%0d%0aCorrelation+ID%3a+d43...%0d%0aTimestamp%3a+2018-09-14+08%3a23%3a55Z&state=33f...
** (exit) an exception was raised:
** (PowAssent.CallbackError) AADSTS90009: Application 'e8b...' is requesting a token for itself. This scenario is supported only if resource is specified using the GUID based App Identifier.
Trace ID: 674...
Correlation ID: d43...
Timestamp: 2018-09-14 08:23:55Z
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:113: PowAssent.Phoenix.AuthorizationController.handle_strategy_error/1
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.action/2
(pow_assent) lib/pow_assent/phoenix/controllers/authorization_controller.ex:1: PowAssent.Phoenix.AuthorizationController.phoenix_controller_pipeline/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
(my_app) lib/plug/debugger.ex:122: MyAppWeb.Endpoint."call (overridable 3)"/2
(my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) c:/Code/my_app/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
Cool, so it's just Azure configuration, and not anything missing in the integration! I've just copied the instructions from Azures own guide so I'll keep it at that. Will get it merged in and release a new version 🚀
v0.1.0-alpha.12
is released.
Awesome, thank you again for all the help, @danschultzer. I have learned a lot through this process and I'm looking forward to being able to contribute back once I've got some more experience.
For future reference; PowAssent.Strategy.AzureOAuth2
has been changed to Assent.Strategy.AzureAD
and now uses OIDC. It requires a nonce, but with PowAssent you just have to add nonce: true
to the provider config.
It would be useful to have support for Azure AD out-of-the-box. Something along the lines of that provided by ueberauth_microsoft, but for Pow instead.
I might implement this myself and submit a pull request, once my skills are sufficient. Opening a feature request here to flag the need and in case somebody else decides to pick it up in the meantime.