Build your own Web API protected by Azure AD v2.0 endpoint with custom scopes

* This post is writing about Azure AD v2.0 endpoint. If you’re using v1, please see “Build your own api with Azure AD (written in Japanese)”.

You can now build your own Web API protected by the OAuth flow and you can add your own scopes with Azure AD v2.0 endpoint (also with Azure AD B2C).
Here I show you how to setup, how to build, and how to consider with custom scopes in v2.0 endpoint. (You can also learn several OAuth scenarios and ideas through this post.)

I note that now your Microsoft Account (consumer account) cannot provide the following scenarios with custom (user-defined) scopes. Please use your organization account (Azure AD account).

Note : For Azure AD B2C, please refer the post “Azure AD B2C Access Tokens now in public preview” in team blog.

Register your own Web API

First we register our custom Web API in v2.0 endpoint, and consent this app in the tenant.

Please go to Application Registration Portal, and start to register your own Web API by pressing [Add an app] button. In the application settings, click [Add Platform] and select [Web API].

In the added platform pane, you can see the following generated scope (access_as_user) by default.

This scope is used as follows.
For example, when you create your client app to access this custom Web API by OAuth, this client can access the following uri for the permissions calling Web API with the scope value.

https://login.microsoftonline.com/common/oauth2/v2.0/authorize
  ?response_type=id_token+code
  &response_mode=form_post
  &client_id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  &scope=openid+api%3a%2f%2f8a9c6678-7194-43b0-9409-a3a10c3a9800%2faccess_as_user
  &redirect_uri=https%3A%2F%2Flocalhost%2Ftest
  &nonce=abcdef

Now let’s change this default scope, and define the new read and write scopes as follows here. (We assume that the scopes are api://8a9c6678-7194-43b0-9409-a3a10c3a9800/read and api://8a9c6678-7194-43b0-9409-a3a10c3a9800/write.)

Note : If the admin consent is needed for using the custom scope (the admin can only use your scope), please edit the manifest manually.

Next we must also add “Web” platform (not “Web API” platform), because the user needs to consent this api application before using these custom scopes.

For example, please remember “Office 365”. The organizations or users who don’t purchase (subscribe) Office 365 cannot use the Office 365 API’s permissions. (No Office 365 API permissions are displayed in their Azure AD settings.) After you purchase Office 365 in https://portal.office.com/, you can start to use these API’s permissions.
Your custom api is the same. Before using these custom scopes, the user have to involve this custom application in the tenant or the individual.

When some user accesses the following url in their web browser and login with the user’s credential, the following consent UI will be displayed. Once the user approves this consent, this custom Web API application is registered in user’s individual permissions. (Note that the client_id is the application id of this custom Web API application, and the redirect_uri is the redirect url on “Web” platform in your custom Web API application. Please change these values to meet your application settings.)

https://login.microsoftonline.com/common/oauth2/v2.0/authorize
  ?response_type=id_token
  &response_mode=form_post
  &client_id=8a9c6678-7194-43b0-9409-a3a10c3a9800
  &scope=openid
  &redirect_uri=https%3A%2F%2Flocalhost%2Ftestapi
  &nonce=abcdef

Note : You can revoke the permission with https://account.activedirectory.windowsazure.com/, when you are using the organization account (Azure AD Account). It’s https://account.live.com/consent/Manage, when you’re using the consumer account (Microsoft Account).

Use the custom scope in your client application

After the user has consented the custom Web API application, now the user can use the custom scopes (api://.../read and api://.../write in this example) in the user’s client application. (In this post, we use the OAuth code grant flow with the web client application.)

First let’s register the new client application in Application Registration Portal with the user account who consented your Web API application. In this post, we create as “Web” platform for this client application (i.e, web client application).

The application password (client secret) must also be generated as follows in the application settings.

Now let’s consume the custom scope (of custom Web API) with this generated web client.
Access the following url with your web browser. (As you can see, the requesting scope is the previously registered custom scope api://8a9c6678-7194-43b0-9409-a3a10c3a9800/read.)
Here client_id is the application id of the web client application (not custom Web API application), and redirect_uri is the redirect url of the web client application.

https://login.microsoftonline.com/common/oauth2/v2.0/authorize
  ?response_type=code
  &response_mode=query
  &client_id=b5b3a0e3-d85e-4b4f-98d6-e7483e49bffc
  &scope=api%3A%2F%2F8a9c6678-7194-43b0-9409-a3a10c3a9800%2Fread
  &redirect_uri=https%3a%2f%2flocalhost%2ftestwebclient

Note : In the real production, it’s also better to retrieve the id token (i.e, response_type=id_token+code), since your client will have to validate the returned token and check if the user has logged-in correctly.
This sample will skip this complicated steps for your understandings.

When you access this url, the following login page will be displayed.

After the login succeeds with the user’s credential, the following consent is displayed.
As you can see, this shows that the client will use the permission of “Read test service data” (custom permission), which is the previously registered custom scope permission (api://8a9c6678-7194-43b0-9409-a3a10c3a9800/read).

After you approve this consent, the code will be returned into your redirect url as follows.

https://localhost/testwebclient?code=OAQABAAIAA...

Next, using code value, you can request the access token for the requested resource (custom scope) with the following HTTP request.
This client_id and client_secret are each application id and application password of the user’s web client application.

HTTP Request

POST https://login.microsoftonline.com/common/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=OAQABAAIAA...
&client_id=b5b3a0e3-d85e-4b4f-98d6-e7483e49bffc
&client_secret=pmC...
&scope=api%3A%2F%2F8a9c6678-7194-43b0-9409-a3a10c3a9800%2Fread
&redirect_uri=https%3A%2F%2Flocalhost%2Ftestwebclient

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "token_type": "Bearer",
  "scope": "api://8a9c6678-7194-43b0-9409-a3a10c3a9800/read",
  "expires_in": 3599,
  "ext_expires_in": 0,
  "access_token": "eyJ0eXAiOi..."
}

Note : If you want to get refresh token, you must add “offline_access” to the scopes.

Using the returned access token (access_token property), you can call your custom Web API as follows and the API can verify the passed token. (Later I show you how to verify this token in your custom Web API.)

GET https://localhost/testapi
Authorization: Bearer eyJ0eXAiOi...

Verify access token in your Web API

Now it’s turn in your custom Web API.

How to check whether the access token is valid ? How to get the logged-in user’s claims ?

First you must remember that v2.0 endpoint returns the following token format.

id token access token
organization account (Azure AD) JWT JWT
consumer account (MSA) JWT Compact Tickets

As you can see in the table above, the passed access token is IETF JWT (Json Web Token) format as follows, if you are using Azure AD account (organization account).

  • JWT has 3 string tokens delimited by the dot (.) character.
  • Each delimited tokens are the base64 url encoded (encoded by RFC 4686).
  • Each delimited tokens (3 tokens) are having :
    Certificate information (ex: the type of key, key id, etc), claim information (ex: user name, tenant id, token expiration, etc), and digital signature (byte code).

For example, the following is PHP example of decoding access token. (The sample of C# is here.)
This sample outputs the 2nd delimited token string (i.e, claims information) as result.

<?php
echo "The result is " . token_test("eyJ0eXAiOi...");

// return claims
function token_test($token) {
  $res = 0;

  // 1 create array from token separated by dot (.)
  $token_arr = explode('.', $token);
  $header_enc = $token_arr[0];
  $claim_enc = $token_arr[1];
  $sig_enc = $token_arr[2];

  // 2 base 64 url decoding
  $header = base64_url_decode($header_enc);
  $claim = base64_url_decode($claim_enc);
  $sig = base64_url_decode($sig_enc);

  return $claim;
}

function base64_url_decode($arg) {
  $res = $arg;
  $res = str_replace('-', '+', $res);
  $res = str_replace('_', '/', $res);
  switch (strlen($res) % 4) {
    case 0:
      break;
    case 2:
      $res .= "==";
      break;
    case 3:
      $res .= "=";
      break;
    default:
      break;
  }
  $res = base64_decode($res);
  return $res;
}
?>

The output result (claim information) is the json string as follows.

{
  "aud": "8a9c6678-7194-43b0-9409-a3a10c3a9800",
  "iss": "https://login.microsoftonline.com/3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15/v2.0",
  "iat": 1498037743,
  "nbf": 1498037743,
  "exp": 1498041643,
  "aio": "ATQAy/8DAA...",
  "azp": "b5b3a0e3-d85e-4b4f-98d6-e7483e49bffc",
  "azpacr": "1",
  "name": "Christie Cline",
  "oid": "fb0d1227-1553-4d71-a04f-da6507ae0d85",
  "preferred_username": "ChristieC@MOD776816.onmicrosoft.com",
  "scp": "read",
  "sub": "Pcz_ssYLnD...",
  "tid": "3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15",
  "ver": "2.0"
}

The aud means the application id for targeting web api (here, custom Web API), nbf (= not before) is the starting time of the token expiration, exp is the expiring time of the token, tid means the tenant id of this logged-in user, and scp is the granted scopes.
With these claim values, your custom Web API can check if the passed token is valid.

Here I show you the PHP sample code for checking these claims.

<?php
echo "The result is " . token_test("eyJ0eXAiOi...");

// return 1, if token is valid
// return 0, if token is invalid
function token_test($token) {
  // 1 create array from token separated by dot (.)
  $token_arr = explode('.', $token);
  $header_enc = $token_arr[0];
  $claim_enc = $token_arr[1];
  $sig_enc = $token_arr[2];

  // 2 base 64 url decoding
  $header = 
    json_decode(base64_url_decode($header_enc), TRUE);
  $claim =
    json_decode(base64_url_decode($claim_enc), TRUE);
  $sig = base64_url_decode($sig_enc);

  // 3 expiration check
  $dtnow = time();
  if($dtnow <= $claim['nbf'] or $dtnow >= $claim['exp'])
    return 0;

  // 4 audience check
  if (strcmp($claim['aud'], '8a9c6678-7194-43b0-9409-a3a10c3a9800') !== 0)
    return 0;

  // 5 scope check
  if (strcmp($claim['scp'], 'read') !== 0)
    return 0;
    
  // other checks if needed (lisenced tenant, etc)
  // Here, we skip these steps ...

  return 1;
}

function base64_url_decode($arg) {
  $res = $arg;
  $res = str_replace('-', '+', $res);
  $res = str_replace('_', '/', $res);
  switch (strlen($res) % 4) {
    case 0:
      break;
    case 2:
      $res .= "==";
      break;
    case 3:
      $res .= "=";
      break;
    default:
      break;
  }
  $res = base64_decode($res);
  return $res;
}
?>

But it’s not complete !

Now let’s consider what if some malicious one has changed this token ? For example, if you are a developer, you can easily change the returned token string with Fiddler or other developer tools and you might be able to login to the critical corporate applications with other user’s credential.

Lastly, the digital signature (the third token in access token string) works against this kind of attacks.

The digital signature is generated using the private key in Microsoft identity provider (Azure AD, etc), and you can verify using the public key which everyone can access. Moreover this digital signature is derived from {1st delimited token string}.{2nd delimited token string} string.
That is, if you change the claims (2nd token string) in access token, the digital signature must be also generated again. And only Microsoft identity provider can create this digital signature. (The malicious user cannot.)

That is, all you have to do is to check whether this digital signature is valid with public key. Let’s see how to do that.

First you can get the public key from https://{issuer url}/.well-known/openid-configuration. (The issuer url is equal to the “iss” value in the claim.) In this case, you can get from the following url.

GET https://login.microsoftonline.com/3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15/v2.0/.well-known/openid-configuration
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "authorization_endpoint": "https://login.microsoftonline.com/3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15/oauth2/v2.0/authorize",
  "token_endpoint": "https://login.microsoftonline.com/3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15/oauth2/v2.0/token",
  "token_endpoint_auth_methods_supported": [
    "client_secret_post",
    "private_key_jwt"
  ],
  "jwks_uri": "https://login.microsoftonline.com/3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15/discovery/v2.0/keys",
  "response_modes_supported": [
    "query",
    "fragment",
    "form_post"
  ],
  "subject_types_supported": [
    "pairwise"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "http_logout_supported": true,
  "frontchannel_logout_supported": true,
  "end_session_endpoint": "https://login.microsoftonline.com/3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15/oauth2/v2.0/logout",
  "response_types_supported": [
    "code",
    "id_token",
    "code id_token",
    "id_token token"
  ],
  "scopes_supported": [
    "openid",
    "profile",
    "email",
    "offline_access"
  ],
  "issuer": "https://login.microsoftonline.com/3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15/v2.0",
  "claims_supported": [
    "sub",
    "iss",
    "cloud_instance_name",
    "cloud_graph_host_name",
    "aud",
    "exp",
    "iat",
    "auth_time",
    "acr",
    "nonce",
    "preferred_username",
    "name",
    "tid",
    "ver",
    "at_hash",
    "c_hash",
    "email"
  ],
  "request_uri_parameter_supported": false,
  "tenant_region_scope": "NA",
  "cloud_instance_name": "microsoftonline.com",
  "cloud_graph_host_name": "graph.windows.net"
}

Next you access to the location of “jwks_uri” property (see above), and you can get public key list from that location. Finally you can find appropriate key by matching the “kid” (key id).

Here I show you the complete code by PHP as follows.

<?php
echo "The result is " . token_test("eyJ0eXAiOi...");

// return 1, if token is valid
// return 0, if token is invalid
function token_test($token) {
  // 1 create array from token separated by dot (.)
  $token_arr = explode('.', $token);
  $header_enc = $token_arr[0];
  $claim_enc = $token_arr[1];
  $sig_enc = $token_arr[2];

  // 2 base 64 url decoding
  $header =
    json_decode(base64_url_decode($header_enc), TRUE);
  $claim =
    json_decode(base64_url_decode($claim_enc), TRUE);
  $sig = base64_url_decode($sig_enc);

  // 3 period check
  $dtnow = time();
  if($dtnow <= $claim['nbf'] or $dtnow >= $claim['exp'])
    return 0;

  // 4 audience check
  if (strcmp($claim['aud'], '8a9c6678-7194-43b0-9409-a3a10c3a9800') !== 0)
    return 0;

  // 5 scope check
  if (strcmp($claim['scp'], 'read') !== 0)
    return 0;

  // other checks if needed (lisenced tenant, etc)
  // Here, we skip these steps ...

  //
  // 6 check signature
  //

  // 6-a get key list
  $keylist =
    file_get_contents('https://login.microsoftonline.com/3bc5ea6c-9286-4ca9-8c1a-1b2c4f013f15/discovery/v2.0/keys');
  $keylist_arr = json_decode($keylist, TRUE);
  foreach($keylist_arr['keys'] as $key => $value) {
    
    // 6-b select one key
    if($value['kid'] == $header['kid']) {
      
      // 6-c get public key from key info
      $cert_txt = '-----BEGIN CERTIFICATE-----' . "n" . chunk_split($value['x5c'][0], 64) . '-----END CERTIFICATE-----';
      $cert_obj = openssl_x509_read($cert_txt);
      $pkey_obj = openssl_pkey_get_public($cert_obj);
      $pkey_arr = openssl_pkey_get_details($pkey_obj);
      $pkey_txt = $pkey_arr['key'];
      
      // 6-d validate signature
      $token_valid =
        openssl_verify($header_enc . '.' . $claim_enc, $sig, $pkey_txt, OPENSSL_ALGO_SHA256);
      if($token_valid == 1)
        return 1;
      else
        return 0;      
    }
  }
  
  return 0;
}

function base64_url_decode($arg) {
  $res = $arg;
  $res = str_replace('-', '+', $res);
  $res = str_replace('_', '/', $res);
  switch (strlen($res) % 4) {
    case 0:
      break;
    case 2:
      $res .= "==";
      break;
    case 3:
      $res .= "=";
      break;
    default:
      break;
  }
  $res = base64_decode($res);
  return $res;
}
?>

Calling another services in turn (OAuth On-Behalf-Of Flow)

As you can see above, the access token is for the some specific api (for “aud“) and you cannot reuse the token for another api.
What if your custom Web API needs to call another api (for ex, Microsoft Graph API, etc) ?

In such a case, your api can convert to another token with OAuth on-behalf-of flow as follows. No need to display the login UI again.
In this example, our custom Web API will connect to Microsoft Graph API and get e-mail messages of the logged-in user.

Note : For a long ago I explained about this on-behalf-of flow in my blog post with Azure AD v1 endpoint, but here I will explain with v2.0 endpoint, because it’s a little tricky …

First, as the official document says (see here), you need to use tenant-aware endpoint when you use on-behalf-of flow with v2.0 endpoint. That is, the administrator consent (admin consent) is needed before requesting the on-behalf-of flow. (In this case, the user consent for custom Web API which is done in the previous section in this post is not needed.)

Before proceeding the admin consent, you must add the delegated permission for your custom Web API in Application Registration Portal. In this example, we add Mail.Read permission as follows. (When you use admin consent, you cannot add scopes on the fly and you must set the permissions beforehand.)

Next the administrator in the user tenant must access the following url using the web browser for administrator consent.
Note that xxxxx.onmicrosoft.com can also be the tenant id (which is the Guid retrieved as “tid” in the previous claims). 8a9c6678-7194-43b0-9409-a3a10c3a9800 is the application id of the custom Web API and https://localhost/testapi is the redirect url of the custom Web API.

https://login.microsoftonline.com/xxxxx.onmicrosoft.com/adminconsent
  ?client_id=8a9c6678-7194-43b0-9409-a3a10c3a9800
  &state=12345
  &redirect_uri=https%3A%2F%2Flocalhost%2Ftestapi

After logged-in with the tenant administrator, the following consent is displayed. When the administrator approves this consent, your custom Web API is registered in the tenant. As a result, all users in this tenant can use this custom Web API and custom scopes.

Note : You can revoke the admin-consented application in your tenant with Azure Portal. (Of course, the administrator privilege is needed for this operation.)

Now you can ready for the OAuth on-behalf-of flow in v2.0 endpoint !

First the user (non-administrator) gets the access token for the custom Web API and call the custom Web API with this access token. This flow is the same as above and I skip the steps here.

Then the custom Web API can request the following HTTP POST for Azure AD v2.0 endpoint using the passed access token. (I note that eyJ0eXAiOi... is the passed access token for this custom Web API, 8a9c6678-7194-43b0-9409-a3a10c3a9800 is the application id of your custom Web API, and itS... is the application password of your custom Web API.)
This POST method is requesting the new access token for https://graph.microsoft.com/mail.read (pre-defined scope).

POST https://login.microsoftonline.com/xxxxx.onmicrosoft.com/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&assertion=eyJ0eXAiOi...
&requested_token_use=on_behalf_of
&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read
&client_id=8a9c6678-7194-43b0-9409-a3a10c3a9800
&client_secret=itS...

The following is the HTTP response for this on-behalf-of request. As you can see, your custom Web API can take new access token.

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "token_type": "Bearer",
  "scope": "https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/User.Read",
  "expires_in": 3511,
  "ext_expires_in": 0,
  "access_token": "eyJ0eXAiOi..."
}

The returned access token is having the scope for Mail.Read (https://graph.microsoft.com/mail.read), and it’s not the application only token, but the user token by the logged-in user, although it’s done by the backend (server-to-server) without user interaction. (Please parse and decode this access token as I described above.)

Therefore, when your custom Web API connects to Microsoft Graph endpoint with this access token, the logged-in user’s e-mail messages will be returned to your custom Web API.

GET https://graph.microsoft.com/v1.0/me/messages
  ?$orderby=receivedDateTime%20desc
  &$select=subject,receivedDateTime,from
  &$top=20
Accept: application/json
Authorization: Bearer eyJ0eXAiOi...

 

[Reference] App types for the Azure Active Directory v2.0 endpoint
https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-flows

 

Advertisements

How to use Azure REST API with Certificate programmatically (without interactive Login UI)

This post is just tips for Azure programming. (One of frequently asked questions by developers)

A long ago, I introduced how to use Azure REST API (in resource manager mode) and role model called RBAC (Role Based Access Control). (See my old post “Manage your Azure resource with REST API (ARM)“.)
In usual cases, API is called with the HTTP header of Azure AD user token, which is retrieved by the OAuth flow and user’s interactive login activity.

But, what if your application is some kind of the backend process like daemon ?

In this post I explain about this scenario as follows.

Note : If you want to run some kind of automation jobs (scheduled or triggered) for Azure resources, you can register your job (scripts) in Azure Automation. Of course, you don’t need to login using interactive UI for running the Automation job.
Here it’s assumed that we access Azure resources from outside of Azure.

What is concerns ?

In terms of AuthN/AuthZ flow in Azure Active Directory (Azure AD), you can use application permissions in order to access some API protected by Azure AD from the backend service like daemon. For more details about application permissions, see “Develop your backend server-side application (Deamon, etc) protected by Azure AD without login UI” (post in Japanese).

For example: if you want to create your application which sync all users’ information (in some specific tenant) in the background periodically, you set the application permission like following screenshot.

If you want to sync all users’ calendar data in some tenant in the background, select the following application permission. (See the following screenshot.)

But unfortunately there’s no application permission for Azure Management API ! Look at the following screenshot.
So, what should we do ?

The answer is “use service principal and RBAC” (not application permissions).

What is the role based access control (RBAC) ? As we saw in my old post “Manage your Azure resource with REST API (ARM)“, RBAC is used for the Azure resource’s access permissions in new resource manager (ARM) mode.
“Role” (ex: Reader role, Contributor role, Backup Operator role, etc, etc) is some set of Azure permissions, and you can assign some role to some users or groups. That is, RBAC provides the granular permissions for Azure resources.
This role assignment cannot only be set in each resource (storage, virtual machine, network, etc), but also can be set in subscription level or resource group level. If some role is assigned in resource group level, the user can access all the resources in this resource group. (i.e, the assignment is inherited through your subscription, resource groups, and each resources.)
What you should remember is that you can assign role to service principal, not only users or groups !

Let’s see the configuration and usage of these combination (together with service principal and RBAC).

Step1 – Register service principal

Here I describe how to configure the service principal.

First you add new service principal in Azure AD tenant as follows.
Login to Azure Portal (https://portal.azure.com), click “Azure Active Directory” on the left navigation, and click “App registrations” menu.

Press “Add” button, and register the new app (i.e, service principal) by pushing “Create” button.

The service principal is successfully generated.
Finally, please copy the application id and app id uri of this service principal (app) in Azure Portal. (We use these values later.)

Step2 – Set Azure permissions with service principal in RBAC

Next, please assign some role to this service principal in Azure Portal.
The following is this procedure. Here in this post, I set the read permission for all the resources in some specific resource group. (We set “Reader” role.)

  1. View the resource group in Azure Portal.
  2. Press “Access control (IAM)”
  3. Press “Add” button.
    Select “Reader” as role and select the previous service principal as members.
  4. Press “Save” button.

Step3A – Retrieve access token with app key

You can retrieve the access token (which is needed for calling Azure rest api) by either app key (app password, client secret) or certificate.
First we explain the case of app key.

To create the app key for your service principal, just you create the key in your service principal with Azure Portal. Select [Settings] and [Keys] in Azure AD application management, and you can generate the key (password string) for your service principal.

For retrieving access token, you just publish the following HTTP request. (The value of client secret is the previously generated app key.)
Note that you must use the tenant-aware url instead of using “https://login.microsoftonline.com/common/oauth2/token”.

HTTP Request (Get access token)

POST https://login.microsoftonline.com/yourtenant.onmicrosoft.com/oauth2/token
Accept: application/json
Content-Type: application/x-www-form-urlencoded

resource=https%3A%2F%2Fmanagement.core.windows.net%2F
&client_id=1c13ff57-672e-4e76-a71d-2a7a75890609
&client_secret=9zVm9Li1...
&grant_type=client_credentials

HTTP Response (Get access token)

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "token_type": "Bearer",
  "expires_in": "3600",
  "ext_expires_in": "10800",
  "expires_on": "1488429872",
  "not_before": "1488425972",
  "resource": "https://management.core.windows.net/",
  "access_token": "eyJ0eXAiOi..."
}

The returned value of access_token attribute is the access token for your Azure REST API calls. Later we use this token value.

Step3B – Retrieve access token with certificate

If you want to use certificate (instead of app key), first you must create and register the certificate into this service principal.
Here we create the self-signed certificate for demo purpose with the following command. (We use makecert utility in Windows SDK.)
As a result, 3 files named “server.pvk”, “server.cer”, and “server.pfx” are generated with this command.

rem -- create self signed CA cert (CA.pvk, CA.cer)
makecert -r -pe -n "CN=My Root Authority" -ss CA -sr CurrentUser -a sha1 -sky signature -cy authority -sv CA.pvk CA.cer

rem -- create self signed server cert (server.pvk, server.cer, server.pfx)
makecert -pe -n "CN=DemoApp" -a sha1 -sky Exchange -eku 1.3.6.1.5.5.7.3.1 -ic CA.cer -iv CA.pvk -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 -sv server.pvk server.cer
pvk2pfx -pvk server.pvk -spc server.cer -pfx server.pfx -pi {password}

The generated “server.cer” file includes the public key. You can get this encoded raw data and base64 encoded thumbprint by the following PowerShell command, and please copy these strings. The result is in raw.txt and thumbprint.txt.

Note that the base64 encoded thumbprint is not the familiar hexadecimal thumbprint string. If you have the hexadecimal thumbprint, you can get this string by converting to the binary and encoding with base64. (You can use online site like here for this encoding …)

$cer =
  New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cer.Import("C:Demotestserver.cer")

# Get encoded raw
$raw = $cer.GetRawCertData()
$rawtxt = [System.Convert]::ToBase64String($raw)
echo $rawtxt > raw.txt

# Get thumbprint
$hash = $cer.GetCertHash()
$thumbprint = [System.Convert]::ToBase64String($hash)
echo $thumbprint > thumbprint.txt

Note : You can also get key pair and .pfx with openssl as follows.
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -config "C:Program Files (x86)Gitsslopenssl.cnf"
openssl pkcs12 -export -out server.pfx -inkey key.pem -in cert.pem
You can also get the hexadecimal thumbprint as follows, and convert to base64 encoded thumbprint.
openssl x509 -in cert.pem -fingerprint -noout

In Azure Portal, please view the previously registered app in Azure AD, and press “Manifest” button. (The application manifest is showed in the editor.)

In the manifest editor, please add the following key value (bold fonts) in the manifest. Here customKeyIdentifier is the base64 encoded thumbprint of certificate (which was retrieved by the previous command), and value is the raw data. Moreover you must create unique GUID for the following keyId.

After that, please press the “Save” button.

{
  "appId": "1c13ff57-672e-4e76-a71d-2a7a75890609",
  "appRoles": [],
  "availableToOtherTenants": false,
  "displayName": "test01",
  "errorUrl": null,
  "groupMembershipClaims": null,
  "homepage": "https://localhost/test01",
  "identifierUris": [
    "https://microsoft.onmicrosoft.com/b71..."
  ],
  "keyCredentials": [
    {
      "customKeyIdentifier": "CTTz0wG...",
      "keyId": "9de40fd2-9559-4b52-b075-04ab17227411",
      "type": "AsymmetricX509Cert",
      "usage": "Verify",
      "value": "MIIDFjC..."
    }
  ],
  "knownClientApplications": [],
  "logoutUrl": null,
  ...

}

Now you start to build your backend (daemon) application.
Before you connect to the Azure resources using REST API, your program must take the access token for the REST API calls.

First you must create the RS256 digital signature using the previous private key (server.pfx). The input string (payload) for this signature must be the base64 uri encoded string of the following 2 tokens delimited by dot ( . ) character. That is, if it’s assumed that the base64 uri encoded string of {"alg":"RS256","x5t":"CTTz0..."} (1st token) is eyJhbGciOi*** (it’ll be large string and here we’re omitting…) and the base64 uri encoded string of {"aud":"https:...","exp":1488426871,"iss":"1c13f...",...} (2nd token) is eyJhdWQiOi***, then the input string must be eyJhbGciOi***.eyJhdWQiOi***. You create digital signature using this input string and previously generated key.

  • x5t is the certificate thumbprint which is previously retrieved by PowerShell command.
  • nbf is the start time (epoch time) of this token expiration, and exp is the end time.
  • iss and sub is your application id (client id).
  • jti is the arbitary GUID which is used for protecting reply attacks.

1st token

{
  "alg": "RS256",
  "x5t": "CTTz0wGaBvl1qhHEmVdw04vExqw"
}

2nd token

{
  "aud": "https://login.microsoftonline.com/microsoft.onmicrosoft.com/oauth2/token",
  "exp": 1488426871,
  "iss": "1c13ff57-672e-4e76-a71d-2a7a75890609",
  "jti": "e86b1f2b-b001-4630-86f5-5f953aeec694",
  "nbf": 1488426271,
  "sub": "1c13ff57-672e-4e76-a71d-2a7a75890609"
}

In order to create the RS256 digital signature for that input string (payload), you can use some libraries. For example: you can use openssl_sign (which needs pem format private key) for PHP programmers, or you might be able to use jsjws for JavaScript. For C# (.NET), I’ll show the complete code later in this chapter.
For more details about this format, please see “Build your API protected by Azure AD (How to verify access token)” (sorry, it’s written in Japanese).

After you’ve created the signature, you can now get the client assertion as follows. This format is the standardized JWT format (see RFC 7519).

{base64 uri encoded string of 1st token}.{base64 uri encoded string of 2nd token}.{base64 uri encoded of generated signature}

Finally you can get access token for Azure REST API with the following HTTP request.
The each attributes are :

  • The url fragment yourtenant.onmicrosoft.com is your tenant domain. In this application flow, you cannot use https://login.microsoftonline.com/common/oauth2/token instead.
  • https://management.core.windows.net/ is the resource id of the Azure REST API (fixed value). The value of resource must be url-encoded string.
  • 1c13ff57-672e-4e76-a71d-2a7a75890609 is the application id of your service principal.
  • eyJhbGciOi... is the client assertion which is previously created. (See above)

HTTP Request (Get access token)

POST https://login.microsoftonline.com/yourtenant.onmicrosoft.com/oauth2/token
Accept: application/json
Content-Type: application/x-www-form-urlencoded

resource=https%3A%2F%2Fmanagement.core.windows.net%2F
&client_id=1c13ff57-672e-4e76-a71d-2a7a75890609
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOi...
&grant_type=client_credentials

The client assertion is checked by the stored raw key (public key), and if it’s verified, the result is successfully returned as follows.

HTTP Response (Get access token)

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "token_type": "Bearer",
  "expires_in": "3600",
  "ext_expires_in": "10800",
  "expires_on": "1488429872",
  "not_before": "1488425972",
  "resource": "https://management.core.windows.net/",
  "access_token": "eyJ0eXAiOi..."
}

The returned value of access_token attribute is the access token for your Azure REST API calls.

As we described above, we saw how to get access token with raw HTTP request.
But if you’re using C# (.NET), you can get access token by a few lines of code as follows with ADAL (Microsoft.IdentityModel.Clients.ActiveDirectory libarary).

...
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Security.Cryptography.X509Certificates;
...

AuthenticationContext ctx = new AuthenticationContext(
  "https://login.microsoftonline.com/yourtenant.onmicrosoft.com/oauth2/authorize",
  false);
X509Certificate2 cert = new X509Certificate2(
  @"C:tmpserver.pfx",
  "P@ssw0rd", // password of private key
  X509KeyStorageFlags.MachineKeySet);
ClientAssertionCertificate ast = new ClientAssertionCertificate(
  "1c13ff57-672e-4e76-a71d-2a7a75890609", // application id
  cert);
var res = await ctx.AcquireTokenAsync(
  "https://management.core.windows.net/",  // resource id
  ast);
MessageBox.Show(res.AccessToken);

Step4 – Connect Azure resources !

Now let’s connect using Azure REST API.

Here we’re getting a Virtual Machine resource using Azure REST API. The point is to set the value of access token as HTTP Authorization header as follows.

HTTP Request (Azure REST API)

GET https://management.azure.com/subscriptions/b3ae1c15-...
  /resourceGroups/TestGroup01
  /providers/Microsoft.Compute/virtualMachines
  /testmachine01?api-version=2017-03-30
Accept: application/json
Authorization: Bearer eyJ0eXAiOi...

HTTP Response (Azure REST API)

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "properties": {
    "vmId": "525f07f9-d4e9-40dc-921f-6adf4ebb8f21",
    "hardwareProfile": {
      "vmSize": "Standard_A2"
    },
    "storageProfile": {
      "imageReference": {
        "publisher": "MicrosoftWindowsServer",
        "offer": "WindowsServer",
        "sku": "2012-R2-Datacenter",
        "version": "latest"
      },
      "osDisk": {
        "osType": "Windows",
        "name": "testmachine01",
        "createOption": "FromImage",
        "vhd": {
          "uri": "https://test01.blob.core.windows.net/vhds/testvm.vhd"
        },
        "caching": "ReadWrite"
      },
      "dataDisks": []
    },
    "osProfile": {
      "computerName": "testmachine01",
      "adminUsername": "tsmatsuz",
      "windowsConfiguration": {
        "provisionVMAgent": true,
        "enableAutomaticUpdates": true
      },
      "secrets": []
    },
    "networkProfile": {
      "networkInterfaces":[
        {
          "id":"..."
        }
      ]
    },
    "diagnosticsProfile": {
      "bootDiagnostics": {
        "enabled": true,
        "storageUri": "https://test01.blob.core.windows.net/"
      }
    },
    "provisioningState": "Succeeded"
  },
  "resources": [
    {
      "properties": {
        "publisher": "Microsoft.Azure.Diagnostics",
        "type": "IaaSDiagnostics",
        "typeHandlerVersion": "1.5",
        "autoUpgradeMinorVersion": true,
        "settings": {
          "xmlCfg":"PFdhZENmZz...",
          "StorageAccount":"test01"
        },
        "provisioningState": "Succeeded"
      },
      "type": "Microsoft.Compute/virtualMachines/extensions",
      "location": "japaneast",
      "id": "...",
      "name": "Microsoft.Insights.VMDiagnosticsSettings"
    }
  ],
  "type": "Microsoft.Compute/virtualMachines",
  "location": "japaneast",
  "tags": {},
  "id": "...",
  "name": "testmachine01"
}

Of course, you can also execute create/update/delete or some other actions (manage, etc) along with the role assignment in your Azure subscription.

 

Build Custom Connector on Microsoft Flow and PowerApps with Authentication

The custom connector (API connector) enables you to connect your own web api (REST api) in Microsoft Flow (including SharePoint workflow) and PowerApps. You can connect Microsoft Flow and PowerApps with your in-house applications or unknown 3rd party (ISV) applications.

In this post I show you how to build and use the custom connector (API connector), and in most cases the authentication is needed, then I also explain with real authentication scenario. (First I explain using Azure AD, and next I show you the other cases, such as Google account.)

Note : Now you can submit your connector for certification and publication. (All users can access your connector.) See “Submit your connectors for Microsoft certification“. (Added on May 2017)

Note : You can now build and publish your API connector with Postman (see here), but here we create with custom swagger configuration. (Added on May 2017)

Build your own web api

In the first example, we use the Azure Active Directory (Azure AD)  as the authentication provider with custom api connector.
In this case, your web api must handle the OAuth access token.

I don’t describe how to build the web api secured by the Azure AD, but if you’re using ASP.NET Web API, you just click [Change Authentication] button in the project creation wizard and set-up the Azure AD information. (See the following screen.)

If you’re using other programming language, see “How to build API secured by Azure AD” (Japanese) in my previous post.

Note : You can also use the Azure App Service (API App) and [Authentication / Authorization] settings (so called “Easy Auth”) for every programming languages.
See “Azure App Service Authentication Deep Dive” (Japanese) for details.

Next you must prepare the swagger specification file (json file).
If you use ASP.NET Web API, you can insert “Swashbuckle” Nuget package and download this json by accessing http://{your root URL}/swagger/docs/v1.

The next is one of the swagger specification example. Especially, you must remember operationId value (the following “Values_Get”), because we use this operation in the PowerApps later.

In the swagger specification, you must add the following “securityDefinitions” section, and set Azure AD authentication information as follows.

{
  "swagger": "2.0",
  "info": {
    "version": "v1",
    "title": "TestApi01"
  },
  "host": "demo.azurewebsites.net",
  "schemes": [
    "https"
  ],
  "paths": {
    "/api/Values/{id}": {
      "get": {
        "tags": [
          "Values"
        ],
        "operationId": "Values_Get",
        "consumes": [
          
        ],
        "produces": [
          "application/json",
          "text/json",
          "application/xml",
          "text/xml"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer",
            "format": "int32"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "string"
            }
          }
        }
      },
      . . .

    }
  },
  "definitions": {
  },
  "securityDefinitions": {
    "oauth2": {
      "type": "oauth2",
      "flow": "implicit",
      "authorizationUrl": "https://login.windows.net/common/oauth2/authorize",
      "scopes": {
        
      }
    }
  }
}

If you use Swashbuckle in your ASP.NET Web API project, you just insert the following code (bold font) in App_StartSwaggerConfig.cs.

public class SwaggerConfig
{
  public static void Register()
  {
    var thisAssembly = typeof(SwaggerConfig).Assembly;

    GlobalConfiguration.Configuration 
      .EnableSwagger(c =>
        {
          c.SingleApiVersion("v1", "TestApi01");
          . . .

          c.OAuth2("oauth2")
            .AuthorizationUrl("https://login.windows.net/common/oauth2/authorize")
            .Flow("implicit")
            .Scopes(scopes =>
            {
              //scopes.Add("xxxxx", "Some access to protected resources");
            });

        })
      .EnableSwaggerUi(c =>
        {
          . . .

        });
  }
}

Note (added Feb 2017) : If you use Azure App Services (including Azure Functions) for hosting your api, you can easily create your swagger definition with security configurations. See “PowerApps team blog : Making it easier to use Azure APIs in PowerApps” for more details.

Register APIs in Azure AD

In the case of Azure AD, the custom connector proxy in the Microsoft Flow or PowerApps retrieves the access token for your web api resource, and calls your web api by setting this token in the http header.
i.e, you must register both the custom connector proxy app and your web api app in the Azure AD, and set the permission between custom connector proxy and your web api.

Note : If you have used the previous [Change Authentication] button in ASP.NET Web API, the web api app is already registered in Azure AD.

The following illustrates this.

When you register the custom connector proxy (Azure AD app of Microsoft Flow or PowerApps side), you must add the following url (fixed value) as the redirect url. Both Microsoft Flow and PowerApps uses this redirect url, when processing OAuth.

https://msmanaged-na.consent.azure-apim.net/redirect

Currently (in Nov 2016), the Azure AD v2 endpoint is not supported (but v1 only) for this scenario, and you must use the Azure Classic Portal (https://manage.windowsazure.com/), not Azure Ibiza Portal (https://portal.azure.com).
And you must set the custom connector proxy’s permissions for accessing your web api. (You cannot set this permission and cannot see the resource id in the Ibiza portal today.)

See the following screenshot.

Note : Strictly speaking, v2.0 endpoint (Azure AD v2 endpoint) is supported in the custom api connector. But, this proxy and web api flow (see the illustration above) is not supported for v2.0 endpoint.
Please refer the next Google scenario (flow) for the v2.0 endpoint.

How to work (or use) in PowerApps

Now you’re ready to use the custom api connector in Microsoft Flow and PowerApps. Here I show you the step of setting PowerApps.

Note : For the details of this procedure, please refer the official document “Register Custom Connectors in PowerApps“.

First you login to PowerApps (https://web.powerapps.com/), and select [Connections] in the navigation, and click [New connection].

Select [Custom] tab and click [New custom API].

In the next window, upload the swagger specification file (json) previously created.

The swagger specification is parsed and the identity provider is auto-detected by the PowerApps.
You must set some api information in the next window like the following screenshot. Note that this client id and secret is for the previous custom connector proxy, not your web api. The Resource URL (the accessing scopes) is the ID/URI of your web api, not the custom connector proxy.

The connection settings of the custom api connector has done. Next you start to create the app in the PowerApps.

Click [New App] button, and select [Phone layout] in the [Blank app].

In the design window, select [Content] – [Data sources] menu.

The data source window is displayed in the right pane, and click [Add data source] button, and [New connection] button.
You can find the previously created custom api connector, and select (connect) that.

Then the Azure AD sign-in UI is displayed, and you must enter your credential.

The custom api connector is inserted as the data source.

Let’s test this custom connector !

Please insert the button control into your app, and input the following expression as the button “OnSelect” function (fx).
This is calling the “ValuesGet” method (see the previous “operationId” in the swagger file) in the custom connector named “TestApi01”, and setting the string result in the context variable named “ResultValue”.

UpdateContext( { ResultValue : TestApi01.ValuesGet(3) } )

Note : You can use auto-correct or suggestions when you’re writing functions.

Next you insert the text box, and set “ResultValue” (the context variable previously created) in the text box function.

Push run button, and this app is launched in the web browser.
When you push the button in the app, your web api is called by the PowerApps and the returned value is displayed in the text box.

In the backend, the following http request is called against your web api.
Then your web api can verify the token (the following “Authorization” header value) and retrieve the claims, as I described before in the post “How to build API secured by Azure AD” (Japanese).

GET /api/Values/3
Accept: */*
Authorization: Bearer eyJ0eXAiOi...
Origin: https://us.create.powerapps.com
X-Ms-Apim-Tokens: ew0KICAidG...
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

"you entered 3"

Note : The “X-Ms-Apim-Tokens” header is also important, and I explain this later. (In the case of Azure AD, there’s no need to use this token.)

This token (“Authorization” header value) is the Azure AD access token iteself. Then you can also get the access token for anothor resources in your web api by calling the following OAuth on_behalf_of flow.
That is, your web api can collaborate another Azure AD resources like Office 365 API, Azure ARM REST, Power BI REST, etc.

POST https://login.microsoftonline.com/common/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion={received access token}&requested_token_use=on_behalf_of&resource={resource id that your api wants to access}&scope=openid&client_id={client id of your web api}&client_secret={client secret of your web api}

You can share your app to other people in the same organization.
The sign-in for this custom connector, i.e, Azure AD sign-in is needed, when the user launch this app for the first time.  (see the following screenshot)

Other Providers (the case of Google)

You can also use the OAuth 2.0 of Google, Facebook, Salesforce and other SaaS applications including the generic OAuth 2.0 providers. (see the official document “Register Custom Connectors in Microsoft Flow“. The flow by API Key and Basic Authentication are also supported.)

Let’s see the case of Google account.

Google and most providers are not having api registration, only client registration. (Except for the app context like api key.)
Therefore, you register only the custom connector proxy as OAuth client into Google Developer Console, get access token for pre-defined Google scopes only (profile, email, etc), and pass this token to your web api. (Or you could use the api key instead.)

Next is the swagger example for Google account settings. (see the bold font)

{
  "swagger": "2.0",
  "info": {
    "version": "v1",
    "title": "TestApi01"
  },
  "host": "demo.azurewebsites.net",
  "schemes": [
    "https"
  ],
  "paths": {
    "/api/Values/{id}": {
      "get": {
        "tags": [
          "Values"
        ],
        "operationId": "Values_Get",
        "consumes": [
          
        ],
        "produces": [
          "application/json",
          "text/json",
          "application/xml",
          "text/xml"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer",
            "format": "int32"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "string"
            }
          }
        }
      },
      . . .

    }
  },
  "definitions": {    
  },
  "securityDefinitions": {
    "oauth2": {
      "type": "oauth2",
      "flow": "accessCode",
      "authorizationUrl": "https://accounts.google.com/o/oauth2/auth",
      "tokenUrl": "https://www.googleapis.com/oauth2/v4/token",
      "scopes": {
        
      }
    }
  }
}

PowerApps (or Microsoft Flow) automatically detects Google account, and when you connect to the custom connector, the Google account login is displayed as the following screenshot.

Next is the http request for your web api.
It is the same like Azure AD, but not. The “Authorization” header value is the access token for Google scopes, not for your web api.

GET /api/Values/3
Accept: */*
Authorization: Bearer ya29.Ci-aAy...
Origin: https://us.create.powerapps.com
X-Ms-Apim-Tokens: ew0KICAidG...

Therefore you cannot verify this access token in your web api, but you can verify the login user instead of using X-Ms-Apim-Tokens. This token (X-Ms-Apim-Tokens) is the Base64 Url encoded value (see RFC 4648) of the following json string, and as you can see, the value includes the refresh token and id token of Google account. As a result, you can decode the id token value, and retrieve the user claims, verify the digital signature.

{
  "token": {
    "AccessToken": "ya29.Ci-aAy...",
    "ExpiresIn": "3600",
    "IdToken": "eyJhbGciOi...",
    "RefreshToken": "1/udVjULwb...",
    "TokenType": "Bearer",
    "OAuth2TokenEndPointCredentialLocation": "Body",
    "ExpiresOn": "636150505441694110",
    "LoginSettingId": "msmanaged-na_customapidemo02.5f989...",
    "TokenAcquireTime": "11/18/2016 6:22:24 AM"
  },
  "sku": "Enterprise",
  "$connectionCreator": {
    "objectId": "cf258756-2623-47cb-be46-c85d436265bb",
    "tenantId": "3c839350-a414-442a-9585-8db0b0f5f300",
    "userPrincipalName": "tsmatsuz@o365directory.onmicrosoft.com"
  },
  "$ConnectionKey": "Key eyJ0eXAiOi...",
  "$callerIdentity": {
    "objectid": "cf258756-2623-47cb-be46-c85d436265bb",
    "prinicipaltype": "ActiveDirectory",
    "tenantid": "3c839350-a414-442a-9585-8db0b0f5f300",
    "email": "tsmatsuz@o365directory.onmicrosoft.com"
  }
}

If your web api doesn’t need the login user, you could use the api key instead.

 

Change logs :

2017/05  renamed “custom api” to “custom connector”

 

How to use Application Permission with Azure AD v2 endpoint and Microsoft Graph

The following scenario of OAuth flow is sometimes needed for the real applications, but this scenario is not supported in the first release of Azure AD v2.0 endpoint. (For v1 endpoint, it’s supported.)

In this blog post, I introduce the recently supported new flow using application permissions with v2.0 endpoint, and explain how it works and how to use with new v2.0 endpoint and Microsoft Graph.

When to use this permission ?

As l wrote in the previous post “How to build backend server-side app (Deamon, etc) using Azure AD v1 endpoint” (sorry, which is written in Japanese), the client credential authentication using application permissions is very strong and powerful OAuth flow for applications.

Imagine that you want to synchronize the organization users in your app with Azure AD users periodically. This sync app would work with no login UI (as daemon or services) and access to the all Azure AD users (read/write).
Using the usual OAuth flow (code grant flow, etc), this is impossible.

The following flow uses the organization-level access privilege using the certificate or password (app secret) without login UI, and can access to the all users, all messages, all calendars, all files, all contacts, all registered devices, etc in the granted organization.
This flow exactly helps the previous scenarios.

Note : The usual user authentication using v2.0 endpoint and Microsoft Graph (which is the commonly used OAuth 2.0 code grant flow) is described in the previous post “OAuth flow by v2.0 endpoint using unified Microsoft identity (Azure AD Account and Microsoft Account)“. (Sorry, it’s also written in Japanese…)

Application Registration

Before using this flow, you must register your application.

You login to https://apps.dev.microsoft.com (the app registration portal), and please add your app pushing “add your app” button.
After the application is created, you can get the application id in the application page. (see the following screenshot)

In “Microsoft Graph Permissions” section on the application page, you can see the following “Application Permissions” area.
In this area, please push “add” button and select “Files.Read.All”.

Note : In the usual v2.0 endpoint flow, you can use the scope (permission) values on the fly. (You don’t need to pre-define this permission values.) But, as I explain later, this flow uses the pre-defined scope attached on your application.

In “Application Secrets” section, you can create the application password (application secret) or asymmetric certificate (key pair). This time, press “Generate New Password” button and create the application secret (password).

Lastly, in “Platforms” section, please press “Add Platform”, add the Web platform, and set your application’s url as redirect uri. (See the following screenshot)
In this example, we use “https://localhost/testapp02&#8221; as redirect uri.

Save all settings.

Admin Consent

We assume that this application (service) is multitenant application and share this service for user’s organization. How to deliver this application for each organization (tenant) ?

In this case, you can use the delivery mechanism called “consent”.
For example, if you use the Facebook integrated application, the application informs you operations (“read your friend list”, “create posts”, etc) the application is needing. If the user approve, the user can use this application. No extra setup is needed.
The application using Microsoft identity is having the same mechanism, and this is called “user consent” which grants the application to access your own data (your messages, your files, etc).

But as I wrote above, this application uses the all organization’s data, not for the specific user. In such a case, the “administrator consent” (admin consent) is used in Azure AD, and this consent must be done by the administrator in the organization.

Note : The admin consent is not only for the application permission, but also used to grant delegated permissions (user permissions) to all users in your organization.

When you use the administrator consent, all you have to do is to go to https://login.microsoftonline.com/{tenant name}/adminconsent?client_id={application id}&state={some state data}&redirect_uri={redirect uri} using web browser.
In this example, we go to the following url. (We assume the user tenant is testdirectory.onmicrosoft.com.)

https://login.microsoftonline.com/testdirectory.onmicrosoft.com/adminconsent?client_id=6abf3364-0a60-4603-8276-e9abb0d843d6&state=12345&redirect_uri=https%3a%2f%2flocalhost%2ftestapp02

Note : The state parameter is returned as the same value after login. Your application can use this data (state), if you want to keep some state after login.

If you access to the above url, the login screen (the following screenshot) is shown. Before using this application, the tenant administrator must login and permit to use this application only once, and the login is needed no longer.
When you are using v2.0 endpoint, you can use both Azure AD Account (organizational account) and Microsoft Account (personal account). But, in this case, this application needs the organization-level permission, then you must login using Azure AD tenant administrator’s account here. (If you use the other kind of account, the error occurs.)

After you login using the administrator account of Azure AD, the following consent screen is displayed. This screen says that if you grant to this application, this app takes the organization-level access privilege.

If you agree this consent, this application is granted to access your organization’s data. After that, the web page is redirected to {redirect uri}?admin_consent=True&tenant={tenant name}&state={state data}. In this example, this is the following url.

https://localhost/testapp02?admin_consent=True&tenant=testdirectory.onmicrosoft.com&state=12345

Note : If the error occurs, the page is redirected to the following uri.
http://localhost/testapp02?error=permission_denied&error_description=The+admin+canceled+the+request

If you don’t need this application anymore, you can revoke this application in Azure Portal. (Soon the new Azure Portal (Ibiza) will support this feature.)

Note : If you change your application permission in this application, the users (tenant administrators) must consent to this application again. (The user can consent many times.)

Get Access Token

Now it’s ready ! Your application can get the access token including permissions (scope or roles) and call some proper operations by the given permissions.

Your application can get access token using the following HTTP request (OAuth).
Note that you cannot use https://login.microsoftonline.com/common/oauth2/v2.0/token (which is commonly used) for getting the token. Instead, you must use https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token, which identifies the specific tenant. (In this example, we use “testdirectory.onmicrosoft.com” as user’s organization.)

The scope must be “https://graph.microsoft.com/.default&#8221;.
In the usual v2.0 endpoint authentication flow, you can use the scope (permission) values on the fly like https://graph.microsoft.com/mail.read. But, this scope (.default) means that the application uses the pre-defined scope (which is “Files.Read.All” in this example).

POST https://login.microsoftonline.com/testdirectory.onmicrosoft.com/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=6abf3364-0a60-4603-8276-e9abb0d843d6&client_secret=JfgrNM9CcW...&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default

Your application can get the following HTTP response. The access token is used for calling APIs (services).

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "token_type": "Bearer",
  "expires_in": 3599,
  "ext_expires_in": 0,
  "access_token": "eyJ0eXAiOi..."
}

Call Services (Microsoft Graph)

Lastly your application calls the service of Microsoft Graph using the provided access token.
In this example, the “Files.Read.All” permission is used for your application, then your application can read the all user’s files using Microsoft Graph in the given organization (testdirectory.onmicrosoft.com).
The following is retrieving all files in the OneDrive root folder for the user “demouser01@testdirectory.onmicrosoft.com”. The result is returning the two folder items.
Your application can get the files of arbitary users in the organization in the same way.

HTTP Request

GET https://graph.microsoft.com/v1.0/users/demouser01@testdirectory.onmicrosoft.com/drive/root/children
Accept: application/json
Authorization: Bearer eyJ0eXAiOi...

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8

{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('demouser01%40testdirectory.onmicrosoft.com')/drive/root/children",
  "value": [
    {
      "createdBy": {
        "user": {
          "id": "cf258756-2623-47cb-be46-c85d436265bb",
          "displayName": "Demo Taro"
        }
      },
      "createdDateTime": "2015-12-22T07:24:46Z",
      "eTag": ""{4C06E4DE-B89C-4A78-848A-FCC6118041F6},1"",
      "id": "01B6UXIPO64QDEZHFYPBFIJCX4YYIYAQPW",
      "lastModifiedBy": {
        "user": {
          "id": "cf258756-2623-47cb-be46-c85d436265bb",
          "displayName": "Demo Taro"
        }
      },
      "lastModifiedDateTime": "2016-05-23T11:45:24Z",
      "name": "testfol",
      "webUrl": "https://o365directory-my.sharepoint.com/personal/demouser01_o365directory_onmicrosoft_com/Documents/testfol",
      "cTag": ""c:{4C06E4DE-B89C-4A78-848A-FCC6118041F6},0"",
      "folder": {
        "childCount": 2
      },
      "parentReference": {
        "driveId": "b!iXgvhy0l90q10oljtqrTKLCkDzZ204FFhgSqDnNXhktfZNOb9jHxQrzimH24Z67t",
        "id": "01B6UXIPN6Y2GOVW7725BZO354PWSELRRZ",
        "path": "/drive/root:"
      },
      "size": 0
    },
    {
      "createdBy": {
        "user": {
          "id": "cf258756-2623-47cb-be46-c85d436265bb",
          "displayName": "Demo Taro"
        }
      },
      "createdDateTime": "2016-05-20T12:57:32Z",
      "eTag": ""{2135DDF2-5458-49E6-8C8E-2AB08501A7E4},1"",
      "id": "01B6UXIPPS3U2SCWCU4ZEYZDRKWCCQDJ7E",
      "lastModifiedBy": {
        "user": {
          "id": "cf258756-2623-47cb-be46-c85d436265bb",
          "displayName": "Demo Taro"
        }
      },
      "lastModifiedDateTime": "2016-05-20T13:04:11Z",
      "name": "expense",
      "webUrl": "https://o365directory-my.sharepoint.com/personal/demouser01_o365directory_onmicrosoft_com/Documents/expense",
      "cTag": ""c:{2135DDF2-5458-49E6-8C8E-2AB08501A7E4},0"",
      "folder": {
        "childCount": 2
      },
      "parentReference": {
        "driveId": "b!iXgvhy0l90q10oljtqrTKLCkDzZ204FFhgSqDnNXhktfZNOb9jHxQrzimH24Z67t",
        "id": "01B6UXIPN6Y2GOVW7725BZO354PWSELRRZ",
        "path": "/drive/root:"
      },
      "size": 0
    }
  ]
}

Using Microsoft Authentication Library (MSAL)

Microsoft Authentication Library (MSAL) is the library that helps you to develop applications that work with v2.0 endpoint. Now this also supports this scenarios.

The following is the C# MSAL sample code, which gets the access token for this application permission’s scenario.
You must install the NuGet package “Microsoft.Identity.Client” (Microsoft Authentication Library, MSAL) in your project.

using Microsoft.Identity.Client;

static void Main(string[] args)
{
  // get access token including application permissions
  ConfidentialClientApplication cl = new ConfidentialClientApplication(
    "https://login.microsoftonline.com/testdirectory.onmicrosoft.com/v2.0",
    "6abf3364-0a60-4603-8276-e9abb0d843d6",
    "https://localhost/testapp02",
    new ClientCredential("JfgrNM9CcW..."),
    new TokenCache());
  AuthenticationResult authResult = cl.AcquireTokenForClient(
    new string[] { "https://graph.microsoft.com/.default" },
    null).Result;

  Console.WriteLine(authResult.Token);
  Console.ReadLine();
}

Azure App Service の Authentication 徹底解説

こんにちは。

Azure App Service に Built-in されている Authentication / Authorization は、簡単な認証・認可を実装したい場合、プログラム コードをいっさい変えずに対処できるため、通称「Easy Auth」とも呼ばれていて、この仕組みをより深く理解しておくと、さまざまな応用が可能になります。(例えば、Mobile App で使用している Backend にカスタム コードからアクセスする、など)

そこで本投稿では、この機能を 敢えて Easy でない領域まで徹底的に解説し、理解を深めたいと思います。(よくご質問もいただくので。。。)

なお、あまりこの仕組みに拘って、無理矢理、高度な使い方をする必要はありません。認証用のコードを直接組み込むなど、従来の手法でプログラミングしても 勿論 OK ですので、適材適所で活用してください。

補足 : 以前、「Azure Mobile Services の Client-directed Login」の投稿で考え方を紹介しましたが、この頃とかなり中身も変わりましたので、改めて本投稿でまとめます。

基本 (Basics)

Azure App Service の Authentication / Authorization は、Web App, Api App, Mobile App, Azure Functions など Azure App Service をベースとしたサービスで共通で使用されています。以降では、それぞれのアプリの形態を意識して、どのような利用方法が可能か、さまざまなシナリオを見ていきますが、サービスごとに機能がわかれているわけではなく、ベースは すべて共通です。

まずは、基本的な設定方法や動きを Web App (画面を持った Web アプリ) を例に紹介します。

Azure Portal で、作成した App Service (Web App, Api App) の [Settings] をクリックします。[Authentication / Authorization] (認証 / 認可) があるので、これをクリックします。
表示される下図の画面で、[App Service Authentication] を [On] にすると、Facebook, Google, Twitter, Microsoft Account, Azure Active Directory (Azure AD) の構成が可能になります。

設定方法はどれも同様で、Facebook, Google などの各 Authentication Provider (Identity Provider) で App (Client) の登録をおこない、そこで取得される Client Id (App Id) や Secret を この Azure App Service Authentication の構成画面 (上図) に設定すれば OK です。(すべての Provider について手順を解説していると冗長になってしまうので、ここではこの設定手順の詳細は説明しません。)

どの Provider (Facebook, Google, etc) も OpenID / OAuth をベースとした認証・認可になりますが、内部で、https://{your domain prefix}.azurewebsites.net/.auth/login/{provider}/callback (例 : https://appdemo01.azurewebsites.net/.auth/login/facebook/callback) のエンドポイントが使用されるので、Provider 側 (Facebook, Google, Microsoft Account, etc) にこの Url を Reply Url (Redirect Uri) として入れておいてください。

また、Azure Active Directory (Azure AD) の構成では Issuer Url を入力しますが、下図の通り、https://sts.windows.net/{tenant id}/ の Url を設定します。この Issuer Url は、特定の tenant のアカウントのみでログインできる点に注意してください。

構成が完了したら、下図のように、その Provider で Login するように設定して Save しましょう。(下図の [Log in with Facebook] を参照。)
下図では、Facebook を構成した場合の例です。

設定は以上で完了です。
プログラム コード (.NET, PHP, Java, Python, etc) のいっさいの変更は不要です。

Web Browser を使ってこの App Service (Web App) のページにアクセスすると、Redirect されて Provider のログイン画面が表示されます。

ID / パスワードを入力してログインすると、Redirect されて App Service (Web App) のページが表示されるはずです。

なお、後述しますが、いくつかの応用的な機能を使う際には、Token Store が必要ですので、このあと解説するいくつかの機能を使用するために、下図の通り、この Token Store の設定を [On] にしておいてください。(このあと解説します。)

補足 : この設定をおこなうと token が Server 上に保管されます。実体は、D:/home/data/.auth/tokens にあります。(Kudu Console などで確認してみてください。)

Token と Cookie

上述の処理の内部では専用の Cookie (AppServiceAuthSession cookie) が使用されています。(なお、Cookie-less の Flow も可能です ! このあと解説します。)
App Service は Provider (Facebook など) の認証に成功すると、Provider (Facebook など) 用の token を受け取り、内部で App Service にアクセスするための専用の token (access token) を生成して、この cookie の中にこうした情報を encrypt して設定しています。以降、App Service (Web App など) では、この cookie の内容を見て認証済が否かなどを判断しています。

このあと解説しますが、上述の [Token Store] を On にした場合は、Provider のログイン完了後に、下記の通り token が付与されて戻ってきます。

/.auth/login/done#token=%7B%22authenticationToken%22%3A...

この token の query string は、下記の Json を Url Encode した文字列であり、下記の authenticationToken が App Service 専用の access token です。このあと解説しますが、この access token を使って Api App などの呼び出しが可能であり、Mobile App SDK などでは、この方法で Backend の Api と連携しています。

{
  "authenticationToken": "eyJ0eXAiOi...",
  "user": {
    "userId": "sid:df003b9147..."
  }
}

選択的な保護 (Deferred Authentication)

上記の通り設定すると、サイト全体 (https://***.azurewebsites.net/ 全体) が保護されますが (認証済でない場合、サイト上のすべてのページで自動で Redirect されます)、一部のページなど選択的に保護する場合は、設定画面で、下図の通り [Allow request (no action)] を選択します。

このように設定すると、基本的にすべてのページにアクセスが可能になり、各ページで認証済か否かの判定を (プログラム コードなどを使って) おこなう必要があります。

ASP.NET の場合は、この設定は [Authorize] 属性で可能です。(細かなコードを記述しなくても、App Service 上の Provider によって自動で処理されます。)
例えば、ASP.NET MVC で /Home/Test の Uri のみを認証によって保護したい場合、下記太字の通り設定します。
認証されていない状態で /Home/Test にアクセスすると、HTTP 401 が返ります。(画面上には、認証エラーが表示されます。)

[Authorize]
public ActionResult Test()
{
  . . .

  return View();
}

なお、ASP.NET 以外 (PHP, Python, etc) の場合、ドキュメント (MSDN) に依れば、自力で cookie (上述の AppServiceAuthSession cookie) を使用するよう記載していますが、Decrypt などに関する情報 (Decrypt で使用する Purpose 等の情報) が提供されていないので、実質、この方法は不可能と思ってください。
認証がおこなわれると、Server Variables の REMOTE_USER にログイン ユーザーの名前が設定されるので、この値を使って判断するなどしてください。(なお、Claim 情報の取得方法については後述します。)
下記は、PHP を使用したサンプルです。

<?php
$authuser = $_SERVER['REMOTE_USER'];
. . .
?>

認証されていないユーザーにログイン ボタンを使ってログインを促す場合、/.auth/login/{provider} (例 : https://appdemo01.azurewebsites.net/.auth/login/facebook) に飛ばすことで、Provider のログイン画面が表示され、ログインに成功すると /.auth/login/done に戻ってきます。
もし、認証後に特定のページ (URL) にリダイレクトさせたい場合は、/.auth/login/{provider}?post_login_redirect_url={redirect url} に飛ばします。
例えば、下記は、ログイン完了後に /Home/Index に飛びます。

https://appdemo01.azurewebsites.net/.auth/login/facebook?post_login_redirect_url=%2FHome%2FIndex

Token の再取得 (refresh token の使用)

内部で token が使用されていることを上述しましたが、実は、この access token には有効期限があり、有効期限が切れた場合は、再度、token を取り直す必要があります。
そこで、Google, Microsoft Account, Azure AD のような refresh token をサポートする Provider を使用した場合、Azure App Service Authentication を使って App Service 用の token の再取得 (取り直し) ができるようになっています。(Facebook など refresh token をサポートしていない Provider の場合は、有効期限が切れた際に、上記のフローを繰り返して token を再取得してください。)

まず準備として、Google の場合は access_type=offline、Microsoft Account の場合は scope=wl.offline_access などを付与して、 Provider 向けの OAuth の処理で refresh token を取得するように構成します。
例えば、Microsoft Account の場合は、下図の通り、Azure App Service Authentication の設定画面で [wl.offline_access] を選択します。

補足 : Azure AD の場合は、こちら の手順に沿って Permission の付与や App Service Authentication の再構成をおこなってください。(少々手順が煩雑なので、ここでの解説はしません。)

あとは上述の通り、Azure App Service 用の token (access token) を取得し、この token が期限切れになったら、同じ Web Browser を使って下記の /.auth/refresh にアクセスすると、内部で access token の再取得をおこなって、下記の通り AppServiceAuthSession の cookie の再設定がおこなわれます。(上述の通り、この cookie 内部には、新しい token の情報が含まれています。)

HTTP Request

GET https://apisdemo01.azurewebsites.net/.auth/refresh
Cookie: AppServiceAuthSession=LHvOyMGXBf...

HTTP Response

HTTP/1.1 200 OK
Set-Cookie: AppServiceAuthSession=rnodJ7gdQl...; path=/; secure; HttpOnly

なお、上記では、再取得した access token が表示されていませんが (cookie の値の中に隠蔽されていますが)、Rest Api を使った access token の再取得方法については後述します。

Profile の取得

上述の通り、Cookie (AppServiceAuthSession) でログイン状態が保持されているため、同じブラウザーを使って下記の /.auth/me にアクセスすることで、現在ログインしているユーザーの基本情報 (Basic Profile) を取得できます。

HTTP Request

GET https://apisdemo01.azurewebsites.net/.auth/me
Cookie: AppServiceAuthSession=IiFbLt6jAa...

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "access_token": "EAAY94JYkQ...",
    "expires_on": "2016-08-21T03:40:01.6583447Z",
    "provider_name": "facebook",
    "user_claims": [
      {
        "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
        "val": "1132990870..."
      },
      {
        "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
        "val": "Tsuyoshi Matsuzaki"
      },
      {
        "typ": "urn:facebook:link",
        "val": "https://www.facebook.com/app_scoped_user_id/1132990870091923/"
      },
      {
        "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
        "val": "Tsuyoshi"
      },
      {
        "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
        "val": "Matsuzaki"
      },
      {
        "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/gender",
        "val": "male"
      },
      {
        "typ": "urn:facebook:locale",
        "val": "ja_JP"
      },
      {
        "typ": "urn:facebook:timezone",
        "val": "9"
      }
    ],
    "user_id": "Tsuyoshi Matsuzaki"
  }
]

同様に、この Basic Profile の Rest Api を使った取得方法は後述します。

Server Side での Claim 情報の取得

Azure App Service 上の Web App, Api App などの中からログインしているユーザー情報などを参照する場合、ASP.NET の場合は、普通に IPrincipal から Claim 情報を取得できます。(Azure App Service 上の Provider が、これらの設定をおこなっています。)
例えば、下記のサンプル コードでは、メールアドレス、氏名などの基本的な情報 (取得可能な Claim の一覧) が返されます。

[Authorize]
public ActionResult Test()
{
  ClaimsPrincipal principal = HttpContext.User as ClaimsPrincipal;
  string res = "";
  foreach (var claim in principal.Claims)
  {
    res += claim.Type + "=" + claim.Value + "<br>";
  }
  ViewBag.Message = res;

  return View();
}

また、Token Store が On の場合、下記の通り、Server 側で HTTP Header から元の Provider (Facebook, Google, etc) の情報が取得できるため、PHP, Java など ASP.NET 以外の Application から使用する場合は、この方法が使えます。
下記サンプルのように、認証した Provider 側 (今回の場合、Facebook) の access token も取得できるため、この token を使ってバックエンドから Provider (Facebook など) の API を呼び出すことも可能です。(例えば、「友達リストの取得」など。上述の通り、あらかじめ、適切な scope を追加しておいてください。)

<?php
  $headers = getallheaders();
  echo $headers['X-Ms-Token-Facebook-Access-Token'];
?>

補足 : Identity Provider によって下記の token が使用できます。(refresh token を取得する際の事前準備は、上述を参照してください。)
X-MS-TOKEN-FACEBOOK-ACCESS-TOKEN
X-MS-TOKEN-GOOGLE-ID-TOKEN
X-MS-TOKEN-GOOGLE-ACCESS-TOKEN
X-MS-TOKEN-GOOGLE-REFRESH-TOKEN
X-MS-TOKEN-MICROSOFTACCOUNT-ACCESS-TOKEN
X-MS-TOKEN-MICROSOFTACCOUNT-AUTHENTICATION-TOKEN
X-MS-TOKEN-MICROSOFTACCOUNT-REFRESH-TOKEN
X-MS-TOKEN-TWITTER-ACCESS-TOKEN
X-MS-TOKEN-AAD-ID-TOKEN
X-MS-TOKEN-AAD-ACCESS-TOKEN
X-MS-TOKEN-AAD-REFRESH-TOKEN

もちろん、ログインユーザーの Principal 名など、基本的な情報も、この X-MS- で始まる下記のヘッダーを使って参照できます。

Cookie-less Flow

さて、上記は、Web App のように Web Browser 上で動く画面を持つ Application を想定しましたが、Api App のように Client プログラムから REST などを使って呼び出す場合は、こうした cookie に頼った方法は使えません。
こうしたプログラムでは、これから紹介する方法で OAuth / REST を処理します。

Cookie-less Flow – Token の取得

このあと解説するように、ここで紹介するフローでは、Cookie の代わりに access token と呼ばれる文字列を使用します。

この access token を取得するには、上述の [Token Store] を On にします。/.auth/login/{provider} にアクセスすると Provider (Facebook など) のログイン画面が表示され、このログイン完了後に、下記の通り token が付与されて戻ってきます。

/.auth/login/done#token=%7B%22authenticationToken%22%3A...

この token の query string は、下記の Json を Url Encode した文字列です。この中の authenticationToken が、App Service 専用の access token になります。

{
  "authenticationToken": "eyJ0eXAiOi...",
  "user": {
    "userId": "sid:df003b9147..."
  }
}

この eyJ0eXAiOi… の文字列は、Identity 業界に居る方なら萌える方も多いと思います。
そうです、「Azure AD を使った API 開発 (access token の verify)」で解説した JWT Token で、この JWT Token を解析することで、token の正しさの確認 (verify) や、Claim 情報の取得などが可能です。

なお、この JWT Token を Decode した場合、下記のような Claim が入っています。(下記は Facebook の場合の例です。)

{
  "stable_sid": "sid:1ead5da614...",
  "sub": "sid:df003b9147...",
  "idp": "facebook",
  "ver": "3",
  "iss": "https://appdemo01.azurewebsites.net/",
  "aud": "https://appdemo01.azurewebsites.net/",
  "exp": 1471744628,
  "nbf": 1466560825
}

Cookie-less Flow – Client-directed Login

上記の方法以外に、Client-directed Login と呼ばれる方法を使って、Facebook SDK など Provider が提供する既存の SDK と組み合わせて access token (App Service 専用の access token) を取得できます。(また、AngularJS など、Front-end 主体のアプリケーションの場合にも、ここで紹介する方法が使えます。)

まず、普通に、各 Provider (Facebook, Google, Twitter, etc) にアクセスして、Provider の access token を取得します。この方法の詳細は、Google, Facebook, Live Services などの各 API のドキュメントを参照してください。
例えば、Microsoft Account の場合は、「Live Services (Outlook.com, OneDrive, etc) 開発」で解説したように、Web Browser (または Web Browser の Component) のアドレス欄に下記を入力して、Microsoft Account のログイン画面を表示します。(使用する client id など、事前準備の詳細は「Live Services (Outlook.com, OneDrive, etc) 開発」を参照してください。)

https://login.live.com/oauth20_authorize.srf
  ?response_type=code
  &client_id=000000004019ED2C
  &scope=wl.signin%20wl.offline_access
  &redirect_uri=https%3A%2F%2Fapisdemo01.azurewebsites.net

Web Browser 上でログインが完了すると、Redirect されて下記の通り、code を付与して戻ります。

https://apisdemo01.azurewebsites.net/?code=Mb2900d43...

アプリケーション側で この戻ってきた code を取得し、この code を使って、下記の通り、HTTP POST を Request すると、Response として、下記の通り、Provider の access token (今回の場合、Microsoft Account の access token) が戻ってきます。

HTTP Request

POST https://login.live.com/oauth20_token.srf
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=Mb2900d43...&client_id=000000004019ED2C&client_secret=P9BjaLvubm...&redirect_uri=https%3A%2F%2Fapisdemo01.azurewebsites.net

HTTP Response

{
  "token_type": "bearer",
  "expires_in": 3600,
  "scope": "wl.signin wl.offline_access",
  "access_token": "EwBwAq1DBA...",
  "refresh_token": "MCXbHXp0i5...",
  "authentication_token": "eyJ0eXAiOi...",
  "user_id": "112c1387ec..."
}

Provider (今回は Microsoft Account) の access token が準備できたら、下記の通り、この Provider 用の access token を使って、App Service 専用の access token が取得できます。(この access token は、上述で取得したものと同じ、App Service 専用の access token です。)

補足 : Google など OpenID Connect が使用できる場合は、codeid_token を使って、下記の通り Request しても構いません。
{"authorization_code":"<received code>", "id_token":"<received id_token>"}

HTTP Request

POST https://apisdemo01.azurewebsites.net/.auth/login/microsoftaccount

{"access_token":"EwBwAq1DBA..."}

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "authenticationToken": "eyJ0eXAiOi...",
  "user": {
    "userId": "sid:3302da51c2..."
  }
}

Cookie-less Flow – Api App の呼び出し

上述の通り、/.auth/login/{provider} で App Service 用の access token を取得したら、その access token を X-ZUMO-AUTH ヘッダーに設定することで、App Service で保護された Api App の呼び出しが可能です。(Cookie は必要ありません。)
例えば、下記は、Azure App Service Authentication で保護された Api App を呼び出すサンプルです。

GET https://apisdemo01.azurewebsites.net/api/values/5
X-ZUMO-AUTH: eyJ0eXAiOi...

Cookie-less Flow – Profile の取得

また、/.auth/me に対しても、X-ZUMO-AUTH ヘッダーを使って、上記同様に Basic Profile を取得できます。(上記では cookie を使っていましたが、下記では、access token を使って Basic Profile を取得します。)

HTTP Request

GET https://apisdemo01.azurewebsites.net/.auth/me
X-ZUMO-AUTH: eyJ0eXAiOi...

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "access_token": "EwBwAq1DBA...",
    "authentication_token": "eyJ0eXAiOi...",
    "expires_on": "2016-06-22T05:29:44.9643501Z",
    "provider_name": "microsoftaccount",
    "refresh_token": "MCRqh8*3K9...",
    "user_claims": [
      {
        "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
        "val": "9c0af81b73..."
      },
      {
        "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
        "val": "Tsuyoshi Matsuzaki"
      },
      {
        "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
        "val": "Tsuyoshi"
      },
      {
        "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
        "val": "Matsuzaki"
      },
      {
        "typ": "urn:microsoftaccount:locale",
        "val": "ja_JP"
      },
      {
        "typ": "urn:microsoftaccount:id",
        "val": "9c0af81b73..."
      },
      {
        "typ": "urn:microsoftaccount:name",
        "val": "Tsuyoshi Matsuzaki"
      }
    ],
    "user_id": "Tsuyoshi Matsuzaki"
  }
]

Cookie-less Flow – Token の再取得 (refresh token の使用)

access token が期限切れになった場合も、X-ZUMO-AUTH ヘッダーを使って、上記同様に /.auth/refresh に HTTP POST することで、access token (App Service 専用の token) の再取得が可能です。(refresh token を使用する場合の事前準備については、上述を参照してください。)

HTTP Request

POST https://apisdemo01.azurewebsites.net/.auth/refresh
X-ZUMO-AUTH: eyJ0eXAiOi...

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "authenticationToken": "eyJ0eXAiOi...",
  "user": {
    "userId": "sid:3302da51c2..."
  }
}

Azure AD Authentication による Api App

上記では X-ZUMO-AUTH という Azure App Service 独自の HTTP Header を使いましたが、実は、Provider が Azure AD の場合のみ、「Azure AD を使った API 連携の Client 開発 (OAuth 2.0)」で解説した手法 (つまり、Provider による普通のフロー) がそのまま使えます。(これは嬉しいですね !)

基本的な利用方法は、「Azure AD を使った API 連携の Client 開発 (OAuth 2.0)」で解説した手法に沿って、Api 側 (Server Side) と Client 側 (Client Side) を Azure AD に登録して、Api 側 (Server Side) の Client Id を Azure App Service Authentication に設定し、あとは普通の OAuth のフローで Client Side から Server Side (Azure App Service) を利用します。

具体的な手順を以下に記述します。

  1. Azure Portal を使って、Azure AD に Server Side の Application を登録します。
    この際、Reply Url には、Azure App Service の Url (および、/.auth/login/aad/callback) を登録します。
  2. Azure AD に Client Side の Application を登録します。(Client 側の Reply Url などは、皆さんのアプリケーションにあわせ、自由に設定して構いません。)
    この際、Client Side から Server Side を呼び出すための Permission を設定します。(下図)
  3. Azure App Service の Authentication として、上記で登録した Server Side の Application の Client Id を設定します。(下図)
  4. Azure AD を使った API 連携の Client 開発 (OAuth 2.0)」で解説した手順で、Client Side の Application を構築して access token を取得します。なお、この際、resource として Server Side の Application を指定します。
    access token を取得したら、「Azure AD を使った API 連携の Client 開発 (OAuth 2.0)」で解説した手順で、Azure App Service の Api App に対して Authorization Header を設定して呼び出します。(上述の X-ZUMO-AUTH の Header は不要です。)

この方法では、「Azure AD : Backend Server-Side アプリの開発 (Daemon など)」で取得した app-only の access token も使用できるので、特に、画面 (UI) を持たない Server アプリケーション (Daemon など) から Azure App Service Authentication にバックエンドでアクセスする場合には有効な手法です。

 

Web Authentication API 紹介 (Windows Hello を使った Edge 開発)

Windows Hello を使った開発

こんにちは。
しばらく投稿が滞ってすみません。(長く、休暇をいただきました。)

Chrome で実装されている Credential Management API や、Microsoft Edge (Preview Build) に実装されている Web Authentication API (本投稿で紹介します) など、パスワードのみに依存しない、統一的で、ユーザーの負荷を軽減し、かつセキュアな認証に対するいくつかの試みが、W3C による標準化の動きと共に提案されています。

今回は、FIDO 2.0 に準拠した W3C の Web Authentication API について、実際の Microsoft Edge におけるプログラミングを見ながら細かく紹介します。

もう何度も本ブログで紹介してきたように、Microsoft では、Microsoft Passport と呼ばれるフレームワークを Windows 10 で導入しています。このフレームワークは、多要素認証 (Device と紐づいた認証) などのパスワードのみに依存しないセキュアな認証方式を、生体認証 (biometrics authentication) と組み合わせることで、より直観的に (入力の手間などユーザーの負荷を軽減して) 実現する仕組みで、FIDO に準拠し、既存技術の拡張の形で実装することで、将来的な相互運用も考慮した設計となっています。

以前投稿した「Windows Hello を使った App 開発」では、Windows の API (UWP の KeyCredentialManager API) を使って、このプログラムからの活用例を紹介しましたが、今回紹介する Web Authentication API は、この処理と同様の概念を Web アプリケーションで実現できる API です。

なお、BUILD 2016 のセッションの中でも紹介していますが、現在 (2016/05)、この Web Authentication API には下記の制限があります。

  • まだ早期ドラフトの実装です (名前空間等、MS-Prefix 実装です)。現在は Microsoft Edge のみで動作します。
  • USB, Bluetooth (BT) などによる External Authenticator (外部 Device による認証) は将来対応予定で、現在は TPM などの Embedded Authenticator (利用 Device に付随した認証) に限定しています。今後、FIDO Device がサポートされれば、この API を使って、wearable 端末も含むポータブルなデバイスを使った認証も可能になるでしょう。 (マイクロソフトの公式な投稿ではありませんが、こちらの記事 なども参照してみてください。。。)

 

概念 (おさらい)

まず、「Windows Hello を使った App 開発」でも紹介したように、Microsoft Passport による大まかな流れを理解しておきましょう。(何度も紹介していますが、復習のため改めて記載します。)

Privatre Key (秘密鍵), Public Key (公開鍵) の key pair を用いて、Privatre Key を Device 側、Public Key を接続する Service 側に保持して、相互に認証をおこないます。
まず最初に、PIN や Windows Hello による生体 (biometrics) 認証などの Device 側の認証 (ネットワーク上に情報が流れない閉じた認証) によって、key pair の作成と、Device 側への private key の登録、public key の取得 (および、この取得した public key の Service 側への登録) をおこないます。以降の認証では、同様に Device 側の認証 (PIN, Windows Hello などによる認証) を使って、格納されている private key を取り出して challenge data (nonce など) にデジタル署名 (Digital Signature) をおこない、Application 側 (Service 側) では、この生成された署名が正しいかどうかを public key を使って検証します。(Azure AD とも、このようにして相互に連携します。)

このため、Web Authentication API で使用する (主な) 関数は 2 つだけです。
まず、makeCredential 関数は、初回の key pair の作成、private key の (Device への) 登録、public key の取得をおこなう関数です。(この際、上述の通り、PIN の入力や、Windows Hello による生体認証がおこなわれます。) Application では、通常、利用開始の最初の 1 回だけ、この関数を呼び出すことになるでしょう。
getAssertion 関数は、登録された private key を用いた challenge data のデジタル署名 (Digital Signature) を生成します。(この際も同様に、PIN の入力や、Windows Hello による生体認証がおこなわれます。) Application における以降の認証では、この getAssertion を毎回呼び出して、Digital Signature を生成します。

 

プログラミング (Programming)

W3C で定義されている API の名前空間は webauthn.makeCredential、webauthn.getAssertion ですが、上述の通り、現在はドラフト仕様に基づく ms-prefix の実装であるため、現在 (2016/05 時点) の Edge の実装では、明示的に msCredentials.makeCredential、msCredentials.getAssertion を使用します。

下記は、これらの API を使った簡単な実装例です。

<!DOCTYPE html>
<html>
<head>
<title>Web Authentication API Test</title>

function make() {
  var accountInfo = {
    rpDisplayName: 'Contoso', // Name of relying party
    userDisplayName: 'Tsuyoshi Matsuzaki' // Name of user account
  };
  var cryptoParameters = [
    {
      type: 'FIDO_2_0',
      algorithm: 'RSASSA-PKCS1-v1_5'
    }
  ];
  
  msCredentials.makeCredential(accountInfo, cryptoParameters)
  .then(function (result) {
    // for debugging
    document.getElementById('credID').value = result.id;
    document.getElementById('publicKey').value = JSON.stringify(result.publicKey);
    //alert(result.algorithm)
    //alert(result.attestation)
  }).catch(function (err) {
    alert('err: ' + err.message);
  });
}

function sign() {
  var credID = document.getElementById('credID').value;
  var filters = {
    accept:[
      {
        type: 'FIDO_2_0',
        id: credID
      }
    ]
  };
  msCredentials.getAssertion('challenge value', filters)
  .then(function(result) {
    //for debugging
    document.getElementById('signature').value = result.signature.signature;    
    document.getElementById('authnrdata').value = result.signature.authnrData;    
    document.getElementById('clientdata').value = result.signature.clientData;    
  }).catch(function (err) {
    alert('err: ' + err.message);
  });
}

</head>
<body>
  <button onclick="make()">Make</button>
  <button onclick="sign()">Sign</button>
  
Credential ID:
Public Key:
Base64 Encoded Signature:
Base64 Encoded AuthnrData:
Base64 Encoded ClientData:
</body> </html>

この Web Application では、[Make] ボタンを押すと、makeCredential 関数が呼ばれて、下図の通り PIN 入力や生体認証 (Windows Hello) が促され、Device 側の認証に成功すると、key pair が作成され、Key Container (Device) に private key が登録されて、下図の通り public key (Json の値) が返ってきます。

返される publicKey は、下記のフォーマットの JSON Web Key と呼ばれる key で、今回、このサンプル プログラムでは、受け取った public key をテキストボックスに書き込んでいますが (上図参照)、通常は、この public key を Application 側 (Service 側) に保持 (記憶) しておいて、今後の認証 (getAssertion の処理) に備えます。

{
  "kty": "RSA",
  "alg": "RS256",
  "ext": false,
  "n": "xCqz2wEsl-3...",
  "e": "AQAB"
}

補足 : makeCredential で返される id (上記サンプルコードの中の credID) は、key identifier です。(「Windows Hello を使った App 開発」で紹介した KeyCredentialManager API の RequestCreateAsync, OpenAsync で渡した引数と同じ位置づけの id です。)
この key identifier は、今後、makeCredential に引数として渡して、あらかじめ指定可能になる予定ですが、現時点では、まだ事前の指定は不可能です。(なお、同じ key identifier を使って何度も makeCredential を呼ぶと、key が上書きされます。)
この key identifier は、一度忘れたら取り出す方法 (API など) はないので注意してください。一度作成したら、アプリ側で必ずおぼえておいてください

[Sign] ボタンを押すと、getAssertion 関数が実行されるため、この場合も、再度、PIN 入力や生体認証 (Windows Hello) が促されます。認証に成功すると、上記の getAssertion 関数の引数に指定した challenge data (今回の場合、「challengae value」の文字列) に対する Digital Signature (デジタル署名) を生成して、この署名の値を返します。(上記サンプル コードの authnrData, clientData については後述します。)

このあと見ていきますが、Application 側では、この受け取った Digital Signature (デジタル署名) が正しいかどうかを、あらかじめ保持 (記憶) しておいた public key を使って検証 (verify) します。(少しコツがあるので、後述)

 

Polyfill を使ったプログラミング

MS-Prefix (独自の名前空間) ではなく、W3C 標準の API を使ってプログラミングしたい場合は、webauthn.js の polyfill が提供されています。
この polyfill を使うと、下記の通り、webauthn.makeCredential、webauthn.getAssertion などを使った標準的なプログラミングが可能です。(返り値の取得方法など、細かな内容も上記と異なっていますので比較してみてください。)

<!DOCTYPE html>
<html>
<head>
<title>Web Authentication polyfill test</title>
http://webauthn.js

function make() {
  var accountInfo = {
    rpDisplayName: 'Contoso', // Name of relying party
    userDisplayName: 'Tsuyoshi Matsuzaki' // Name of user account
  };
  var cryptoParameters = [
    {
      type: 'ScopedCred',  // also 'FIDO_2_0' is okay !
      algorithm: 'RSASSA-PKCS1-v1_5'
    }
  ];
  webauthn.makeCredential(accountInfo, cryptoParameters)
  .then(function (result) {
    document.getElementById('credID').value = result.credential.id;
    document.getElementById('publicKey').value = JSON.stringify(result.publicKey);
  }).catch(function (err) {
      alert('err: ' + err.message);
  });
}

function sign() {
  webauthn.getAssertion('challenge value')
  .then(function(result) {
    document.getElementById('signature').value = result.signature;    
    document.getElementById('authnrdata').value = result.authenticatorData;    
    document.getElementById('clientdata').value = result.clientData;    
  }).catch(function (err) {
    alert('err: ' + err.message);
  });
}

</head>
<body>
  <button onclick="make()">Make</button>
  <button onclick="sign()">Sign</button>
  
Credential ID:
Public Key:
Base64 Encoded Signature:
Base64 Encoded AuthnrData:
Base64 Encoded ClientData:
</body> </html>

なお、このサンプルでは、上記の通り、Credential Id (前述の key identifier) を指定せずに getAsserion を呼び出せていますが、polyfill 内部で Credential Id を indexedDB に保持しています。(最終的にブラウザーに実装された場合は、ここはブラウザー側の組み込み機能として実装されることになるでしょう。)

 

FIDO 2 準拠な Signature 検証 (Verify)

上記で取得した Digital Signature (デジタル署名) は、「Azure AD を使った Service (API) 開発 (access token の verify)」で紹介したように、RSA に基づく標準的な方法 (例えば、PHP の場合は、openssl_verify 関数を使うなど) で検証できます。
ただし、いくつか FIDO 2.0 の署名の扱いに関するお作法 (注意点) があるのでコードと一緒に紹介しておきます。(この仕様の詳細については「W3C : Web API for accessing FIDO 2.0 credentials」を参照してください。)

まず、この取得されるデジタル署名 (Digital Signature) は、上記の challenge data (getAssertion の第一引数) に対する直接的な署名ではありません。
上記の getAssertion の結果返ってくる authnrData (authenticatorData) と clientData の Hash の文字列結合を challenge としたデジタル署名 (Digital Signature) となっています。なお、この clientData は、getAssertion で渡した challenge data (第一引数) を使った下記フォーマットの JSON データを Base64 Url エンコードした文字列 (Base64 エンコードをおこない、+ を -、/ を _ に変換して、= を削除した文字列) ですので、意味的には getAssertion で指定した challenge data と同義です。(Json 化されただけと思ってください。)

{ "challenge" : "challenge value" }

このため、Digital Signature の検証は、例えば、PHP で記述した場合は、下記の通りとなります。(下記の $sig_enc が、検証対象の Digital Signature です。)

なお、上述の通り、今回使用する public key は、JSON 形式の modulus (上記 jwt の “n”) と exponent (上記 jwt の “e”) の指定された RSA 公開鍵のため、PEM エンコードを使って下記の通り PHP (Openssl) で扱える public key に変換してから、openssl_verify 関数で検証しています。(この他に、Crypt_RSA を使う方法などもあるでしょう。)

<?php
function verify_signature() {
  // input value (see previous)
  $challenge = 'challenge value';
  $sig_enc = 'nF7SxLHfOd...';
  $n = 'xCqz2wEsl-3...';
  $e = 'AQAB';
  $authnrdata_enc = 'AQAAAAA';
  $clientdata_enc = 'ew0KCSJjaG...';
  
  // return 1, if signature is valid
  // return 0, if signature is invalid
  $res = 0;
  
  // get signature
  $sig = base64_url_decode($sig_enc);
  
  // get signed target input (nonce)
  $authnrdata = base64_url_decode($authnrdata_enc);
  $clientdata = base64_url_decode($clientdata_enc);
  $signeddata = $authnrdata . hash('sha256', $clientdata, true);

  // get public key
  $modulus = strtr($n, '-_', '+/'); // "=" erased base64 encode
  $exponent = strtr($e, '-_', '+/'); // "=" erased base64 encode
  $cert_data = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA' . $modulus . 'ID' . $exponent;
  $cert_txt = '-----BEGIN PUBLIC KEY-----' . "rn"
    . wordwrap($cert_data, 64, "rn", true) . "rn"
    . '-----END PUBLIC KEY-----';
  $pkey_obj = openssl_pkey_get_public($cert_txt);
  $pkey_arr = openssl_pkey_get_details($pkey_obj);
  $pkey_txt = $pkey_arr['key'];
  $res = $pkey_txt;

  // verify signature
  $res = openssl_verify($signeddata, $sig, $pkey_txt, OPENSSL_ALGO_SHA256);
  // if error occured, please check as following
  // $test = openssl_error_string();

  return $res;
}

// Helper functions
function base64_url_decode($arg) {
  $res = $arg;
  $res = strtr($res, '-_', '+/');
  switch (strlen($res) % 4) {
    case 0:
      break;
    case 2:
      $res .= "==";
      break;
    case 3:
      $res .= "=";
      break;
    default:
      break;
  }
  $res = base64_decode($res);
  return $res;
}
?>

 

Origin (Domain) による Isolation

Windows Hello を使った App 開発」で紹介したように、UWP の KeyCredentialManager API では、User ごと、App ごとに使用する key が分離されていました。(例えば、App をまたがって key の共有は不可能でした。)
これと同様、この Web Authentication API では、User ごと、Domain (Origin) ごとに分離されます。

例えば、Web サイト A で作成した key を、同じ key identifier を指定して Web サイト B から使おうとしても、セキュリティ上の観点から不可能です。(もしこれができてしまうと、別のサイトから乗っ取られてしまいますね。)

つまり、KeyCredentialManager のときと同様、この API も、Azure AD や Office 365 など、別のサービスの認証に使うことはできないので注意が必要です。あくまでも、皆さん自身の Web Application の認証に使うための API と考えてください。

 

※ 参考情報

[Windows Blog] A world without passwords: Windows Hello in Microsoft Edge
https://blogs.windows.com/msedgedev/2016/04/12/a-world-without-passwords-windows-hello-in-microsoft-edge/

 

Azure AD B2C の Page UI Customize の実践

こんにちは。

「Active Directory & Security Conference 2016」で紹介した Azure Active Directory B2C (Azure AD B2C) は、実運用の際のカスタマイズを前提としており、パイプライン レベルも含む さまざまな機能変更や拡張が可能になる予定です。(現在は、一部機能が Public Preview として提供されています。)

今回は、B2C のカスタマイズ手法 (How-To) の 1 つである Page UI Customization を紹介します。

Page UI Customization の概要

Azure AD B2C が提供する既定の画面は、下図の通り、何とも殺風景な UI です。(下図は Sign-up の際の Identity Provider の Seletor 画面の例です。)

Default Page (No Customization)

上述の通り、現実の利用ではカスタマイズを前提としていて、Azure AD B2C では、下記の通り、使用する Page のほとんどをカスタマイズ可能です。

  • Sign-up 時の Identity Provider (IdP) の選択ページ
  • Sign-in 時の Identity Provider (IdP) の選択ページ
  • Local Account (IdP と連携しない場合) の Sign-up ページ
  • Social Account (IdP と連携した場合) の Sign-up ページ
  • Multi-factor Authentication (MFA) ページ
  • Sign-In 時の Error ページ
  • Sign-Up 時の Error ページ

補足 : Local Account の Sign-In Page などのカスタマイズが提供されていませんが、現状、これらについては、従来の Azure AD の Branding Customize を使用します。また、よくある IdP Selector と Local Account Sign-In の混合 UI (下図) なども、現状は不可能です。

Page UI Customization の基本的な考え方は、HTML の Custom Page を作成してインターネット上の見える場所に置いておき、この URL を B2C に設定するだけです。(ただし、サーバーサイド ロジックは記述できません。)
しかし、実際にカスタマイズするとわかりますが、HTML を変更しただけでは素直に反映されず、いろいろと理解しておくポイントやコツがあります。以降で、さまざまなカスタマイズのケースを想定し、この具体的方法を解説します。

基本手順

まずは基本手順に沿って、とにかくカスタマイズをしてみましょう。
今回は、「Sign-up 時の Identity Provider (IdP) の選択ページ」を例に紹介します。(今回は、Identity Provider として、Microsoft Account と Facebook が設定されていると仮定します。)

まずは下記のような簡単な Custom Page の HTML ソースを作成し、インターネット上の見える場所に公開します。特に、下記の data-name="IdpSelections" は重要な属性なので、必ず含めておいてください。

<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
  </head>
  <body>
    
Welcome to B2C Demo !

なお、B2C の Page 上の JavaScript (実際には RequireJS) がこの HTML を取得するので、配置の際は CORS を有効にしておきます。
例えば、Azure Web App (Azure App Services) を使用してホストする場合は、下図の通り Azure Portal 上で CORS を有効にしておきます。

上記の Custom Page を B2C に反映するため、Azure Portal 上の Azure AD B2C の管理画面で、今回対象となる SignUp Policy を選択して、[Page UI customization] – [Identity provider selection page] を選択します。(下図)

補足: おそらく Preview 段階のバグだと思いますが、もし上図の [Page UI customization] がクリックできない場合 (Disable の場合) は、いったん [Run Now] ボタンを押してから設定してください。

表示される設定画面で、上記で作成した Custom page の URI を入力して (下図)、設定を保存します。

動作を確認するには、下図の [Run Now] ボタンを押すか、ブラウザーを起動して下記の URL にアクセスします。
なお、下記の b2cdemo01.onmicrosoft.com (テナント名)、B2C_1_testsignup (Policy 名)、b8485bfb-3178-4041-bb2c-7cee34652a72 (Client Id)、https://localhost/test01 は、皆さんの環境にあわせて適宜設定してください。

https://login.microsoftonline.com/b2cdemo01.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1_testsignup&client_Id=b8485bfb-3178-4041-bb2c-7cee34652a72&nonce=testtest&redirect_uri=https%3A%2F%2Flocalhost%2Ftest01&scope=openid&response_type=id_token&prompt=login

アクセスすると、下図の Sign-up 画面が表示されるはずです。

Default Customization UI (No Design Customization)

Design カスタマイズ

ブラウザーの F12 開発者ツールなどを使って、生成された HTML のソースを見ると、最終的に下記ソースが生成されているのがわかります。(ただし、下記は一部を省略しています。)

この Simple な HTML と、Azure AD B2C の既定の Style (下記の hrd.styles-1.0.1.css など) によって、上図のような UI が構築されています。



  
    Home
    
    
    
  
  
  
Welcome to B2C Demo !

Sign Up

  • Email signup
  • Facebook
  • Microsoft Account
</div> </body> </html>

上記の通り、今回は、Microsoft、Facebook、Local Account の SignUp の 3 種類のボタンが表示され、これらボタンに MicrosoftAccountExchange、FacebookExchange、SignUpWithLogonEmailExchange の id が付与されています。CSS では、これらボタンの DOM の id を使って style が適用されています。

即ち、要素の Design をおこなう際は、これら既定の style の override をおこないます。(Custom Page の HTML ソースを変えても、上記の既定の style によって強制的にスタイルが適用されてしまうので注意してください。)

例えば、Custom Page を下記の通り構築してみましょう。今回は、inline style を使って、ボタンのそれぞれに色を付け、縦に配置しています。

<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <style type="text/css">
      .intro {
        position:absolute;
        left:83px;
        font-weight:bold;
        font-style:normal;
        font-size:25px;
        text-align:center;
        line-height:normal;
      }  
      #FacebookExchange {
        position:absolute;
        left:83px;
        top:118px;
        width:250px;
        height:46px;
        margin:0px;
        font-weight:400;
        font-style:normal;
        font-size:15px;
        color:#FFFFFF;
        text-align:center;
        border:none;
        background-color: rgba(59, 87, 156, 1);
      }
      #MicrosoftAccountExchange {
        position:absolute;
        left:83px;
        top:173px;
        width:250px;
        height:46px;
        margin:0px;
        font-weight:400;
        font-style:normal;
        font-size:15px;
        color:#FFFFFF;
        text-align:center;
        border:none;
        background-color: rgba(40, 176, 229, 1);
      }
      #SignUpWithLogonEmailExchange {
        position:absolute;
        left:83px;
        top:259px;
        width:250px;
        height:46px;
        margin:0px;
        font-weight:400;
        font-style:normal;
        font-size:15px;
        color:#FFFFFF;
        text-align:center;
        border:none;
        background-color: rgba(43, 181, 115, 1);
      }
    </style>
  </head>
  <body>
    
Welcome to B2C Demo !

結果は、下図の通り表示されます。それっぽい感じになってきましたね。

Design Customization

例えば、下記太字の通り、Social Account (Facebook, Microsoft Account 等) と Local Account の間に「or」を入れてみましょう。



  
    Home
    
    
      .intro {
        position:absolute;
        left:83px;
        font-weight:bold;
        font-style:normal;
        font-size:25px;
        text-align:center;
        line-height:normal;
      }  
      #FacebookExchange {
        position:absolute;
        left:83px;
        top:118px;
        width:250px;
        height:46px;
        margin:0px;
        font-weight:400;
        font-style:normal;
        font-size:15px;
        color:#FFFFFF;
        text-align:center;
        border:none;
        background-color: rgba(59, 87, 156, 1);
      }
      #MicrosoftAccountExchange {
        position:absolute;
        left:83px;
        top:173px;
        width:250px;
        height:46px;
        margin:0px;
        font-weight:400;
        font-style:normal;
        font-size:15px;
        color:#FFFFFF;
        text-align:center;
        border:none;
        background-color: rgba(40, 176, 229, 1);
      }
      #paragraph1 {
        position:absolute;
        left:83px;
        top:216px;
        width:250px;
        text-align:center;
        font-size:13px;        
        white-space:nowrap;
      }
      #SignUpWithLogonEmailExchange {
        position:absolute;
        left:83px;
        top:259px;
        width:250px;
        height:46px;
        margin:0px;
        font-weight:400;
        font-style:normal;
        font-size:15px;
        color:#FFFFFF;
        text-align:center;
        border:none;
        background-color: rgba(43, 181, 115, 1);
      }
    
  
  
    
Welcome to B2C Demo !

----- OR -----

</body> </html>

下図の通り表示されます。

Inserted Paragraph

外部ファイルも指定できますので、上記のような inline style を使わず、下記太字の通り css file を使っても構いません。

<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <link href="https://www.contoso.com/test.css" type="text/css" rel="stylesheet"/>
  </head>
  <body>
    
Welcome to B2C Demo !
<div id="api" data-name="IdpSelections" /> </body> </html>

また同様に、外部のイメージ ファイル (jpg, png, 等) も使用できるので、下図の通りボタンにアイコンを付けたり、デモでお見せしたようにヘッダー (サービス名など) を装飾するなど、細かな Design が可能です。
B2C では RequireJS を使って Page を取得しているため、この辺りは、普通の HTML Page 同等の編集が可能です。

Inserted Image Icon

なお、ボタンのラベルは Policy のプロパティとして指定可能ですので、下図のように B2C の設定画面を使って変更可能です。(B2C のポリシー定義ファイル (XML) を編集して、アップロードしても構いません。)

ラベルを変えた際の実行結果は、下図の通りです。

Label Customization

 

今回は Sign-Up 時の IdP (Identity Provider) Selector 画面を例に紹介しましたが、他の Page カスタマイズについても同様に柔軟に編集できるので、裏側でスケーラブルな Azure AD B2C が使われていることを利用者に意識させずに、ID 統合環境をアプリに組み込めます。

著名な Real Madrid C.F. のファン サイトは、こんな感じです。(Azure AD B2C を使用しています。)

“Real” Example (Madridista Fan Clubs – Real Madrid CF)