-
-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
910f6e9
commit bc329a1
Showing
3 changed files
with
150 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
strategy: PowAssent.Strategy.AzureOAuth2, | ||
tenant_id: "8eaef023-2b34-4da1-9baa-8bc8c9d6a490" | ||
] | ||
] | ||
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", | ||
strategy: PowAssent.Strategy.AzureOAuth2, | ||
tenant_id: "8eaef023-2b34-4da1-9baa-8bc8c9d6a490", | ||
resource: "https://service.contoso.com/" | ||
] | ||
] | ||
## 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |