Skip to content

Commit

Permalink
Azure OAuth2 strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
danschultzer committed Sep 13, 2018
1 parent 910f6e9 commit 8c8d73f
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Use Google, Github, Twitter, Facebook, or add your custom strategy for authoriza
* [OAuth 1.0](lib/pow_assent/strategies/oauth.ex)
* [OAuth 2.0](lib/pow_assent/strategies/oauth2.ex)
* Includes the following provider strategies:
* [Azure AD](lib/pow_assent/strategies/azure_oauth2.ex)
* [Basecamp](lib/pow_assent/strategies/basecamp.ex)
* [Discord](lib/pow_assent/strategies/discord.ex)
* [Facebook](lib/pow_assent/strategies/facebook.ex)
Expand Down
98 changes: 98 additions & 0 deletions lib/pow_assent/strategies/azure_oauth2.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
defmodule PowAssent.Strategy.AzureOAuth2 do
@moduledoc """
Azure AD OAuth 2.0 strategy.
## Usage
config :my_app, :pow_assent,
providers:
[
azure: [
client_id: "REPLACE_WITH_CLIENT_ID",
client_secret: "REPLACE_WITH_CLIENT_SECRET",
strategy: PowAssent.Strategy.AzureOAuth2
]
]
A tenant id can be set to limit scope of users who can get access (defaults
to "common"):
config :my_app, :pow_assent,
providers:
[
azure: [
client_id: "REPLACE_WITH_CLIENT_ID",
client_secret: "REPLACE_WITH_CLIENT_SECRET",
tenant_id: "8eaef023-2b34-4da1-9baa-8bc8c9d6a490",
strategy: PowAssent.Strategy.AzureOAuth2,
]
]
The resource that client should pull a token for defaults to
`https://graph.microsoft.com/`. It can be overridden with the
`resource` key (or the `authorization_params` key):
config :my_app, :pow_assent,
providers:
[
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
]
]
## Setting up Azure AD
Login to Azure, and set up a new application:
https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code#register-your-application-with-your-ad-tenant
* `client_id` is the "Application ID".
* `client_secret` has to be created with a new key for the application.
* The callback URL (http://localhost:4000/auth/azure/callback) should be
added to Reply URL's for the application
* "Sign in and read user profile" permission has to be enabled.
* To find the App ID URI to be used for `resource`, in the Azure Portal,
click Azure Active Directory, click Application registrations, open the
application's Settings page, then click Properties.
"""
use PowAssent.Strategy.OAuth2.Base

@spec default_config(Keyword.t()) :: Keyword.t()
def default_config(config) do
tenant_id = Keyword.get(config, :tenant_id, "common")
resource = Keyword.get(config, :resource, "https://graph.microsoft.com/")

[
site: "https://login.microsoftonline.com",
authorize_url: "/#{tenant_id}/oauth2/authorize",
token_url: "/#{tenant_id}/oauth2/token",
authorization_params: [response_mode: "query", response_type: "code", resource: resource],
get_user_fn: &get_user/2
]
end

@spec normalize(Client.t(), Keyword.t(), map()) :: {:ok, map()}
def normalize(_client, _config, user) do
{:ok, %{
"uid" => user["sub"],
"name" => user["name"],
"email" => user["email"],
"first_name" => user["given_name"],
"last_name" => user["family_name"]}}
end

@spec get_user(Keyword.t(), Client.t()) :: {:ok, map()}
def get_user(_config, client) do
user =
client.token.other_params["id_token"]
|> String.split(".")
|> Enum.at(1)
|> Base.decode64!(padding: false)
|> Poison.decode!()

{:ok, user}
end
end
51 changes: 51 additions & 0 deletions test/pow_assent/strategies/azure_oauth2.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
defmodule PowAssent.Strategy.AzureOAuth2 do
use PowAssent.Test.Phoenix.ConnCase

import OAuth2.TestHelpers
alias PowAssent.Strategy.AzureOAuth2

@access_token "access_token"

setup %{conn: conn} do
bypass = Bypass.open()
config = [site: bypass_server(bypass)]

{:ok, conn: conn, config: config, bypass: bypass}
end

test "authorize_url/2", %{conn: conn, config: config} do
assert {:ok, %{conn: _conn, url: url}} = AzureOAuth2.authorize_url(config, conn)
assert url =~ "/common/oauth2/authorize?client_id="

config = Keyword.put(config, :tenant_id, "8eaef023-2b34-4da1-9baa-8bc8c9d6a490")
assert {:ok, %{conn: _conn, url: url}} = AzureOAuth2.authorize_url(config, conn)
assert url =~ "/8eaef023-2b34-4da1-9baa-8bc8c9d6a490/oauth2/authorize?client_id="
end

describe "callback/2" do
setup %{conn: conn, config: config, bypass: bypass} do
params = %{"code" => "test", "redirect_uri" => "test"}

{:ok, conn: conn, config: config, params: params, bypass: bypass}
end

test "normalizes data", %{conn: conn, config: config, params: params, bypass: bypass} do
Bypass.expect_once(bypass, "POST", "/common/oauth2/token", fn conn ->
id_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiIyZDRkMTFhMi1mODE0LTQ2YTctODkwYS0yNzRhNzJhNzMwOWUiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83ZmU4MTQ0Ny1kYTU3LTQzODUtYmVjYi02ZGU1N2YyMTQ3N2UvIiwiaWF0IjoxMzg4NDQwODYzLCJuYmYiOjEzODg0NDA4NjMsImV4cCI6MTM4ODQ0NDc2MywidmVyIjoiMS4wIiwidGlkIjoiN2ZlODE0NDctZGE1Ny00Mzg1LWJlY2ItNmRlNTdmMjE0NzdlIiwib2lkIjoiNjgzODlhZTItNjJmYS00YjE4LTkxZmUtNTNkZDEwOWQ3NGY1IiwidXBuIjoiZnJhbmttQGNvbnRvc28uY29tIiwidW5pcXVlX25hbWUiOiJmcmFua21AY29udG9zby5jb20iLCJzdWIiOiJKV3ZZZENXUGhobHBTMVpzZjd5WVV4U2hVd3RVbTV5elBtd18talgzZkhZIiwiZmFtaWx5X25hbWUiOiJNaWxsZXIiLCJnaXZlbl9uYW1lIjoiRnJhbmsifQ."

send_resp(conn, 200, Poison.encode!(%{access_token: @access_token, id_token: id_token}))
end)

expected = %{
"uid" => "JWvYdCWPhhlpS1Zsf7yYUxShUwtUm5yzPmw_-jX3fHY",
"name" => "Frank Miller",
"given_name" => "Frank",
"family_name" => "Miller",
"email" => "frank@contoso.com",
}

{:ok, %{user: user}} = AzureOAuth2.callback(config, conn, params)
assert expected == user
end
end
end

0 comments on commit 8c8d73f

Please sign in to comment.