Uncategorized

Build your own Web API protected by Entra ID

OAuth : App development working with Microsoft Entra ID (Azure AD)

  1. Build Standard Web or Native Application
  2. Build JavaScript Application
  3. Build Backend Application (Daemon app, Service app)
  4. How to Verify Token
  5. Build Your Custom API Application
  6. Workload Identity Federation

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

I note that : As I described in “How to verify id token in Entra ID“, the access token in consumer account (Microsoft Account) is not resolved in your own application. (It’s in black-box by Microsoft.) For this reason, you cannot build your custom API application in consumer accounts (Microsoft Account).
Please use organization accounts (Entra ID accounts) for realizing the following scenarios.

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

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

First, you login to Azure Portal and go to “Azure Active Directory” (Entra ID). In Entra ID management, click “App registrations” in the navigation, and then push “New registration” to register your API app. In app registration wizard, be sure to select an option “Accounts in any organizational directory (Any Azure AD directory – Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)” (i.e, which means v2 endpoint application).

In application page, click “Expose an API” in the navigation and add 2 scopes, “read” and “write” scopes. (We assume that each scopes are api://f6da5452-7f05-4182-bd2d-feac1d2e86e2/read and api://f6da5452-7f05-4182-bd2d-feac1d2e86e2/write.)

This scope is used as follows.
For example, when a client application (remote application) wants to read some data from your custom Web API, this client application should call the following OAuth request for permission, then the client application can get access token for calling your Web API with “read” scope.
Your custom Web API, in turn, should check this incoming access token and grant this application to access your resource.

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%2ff6da5452-7f05-4182-bd2d-feac1d2e86e2%2fread
  &redirect_uri=https%3A%2F%2Flocalhost%2Ftest
  &nonce=abcdef

In the real scenario, your custom Web API will also have to provide user interface for giving permissions.
For example, please remember “Office 365”. The organizations or users who don’t purchase (subscribe) Office 365 cannot take the Office 365 API’s permissions. (No Office 365 API permissions are displayed in their Entra ID settings.) After you (or organization’s admin) purchase Office 365 in https://portal.office.com/, you can start to use these APIs.
Your custom API is the same. Before using these custom scopes, the user have to include this custom application in user’s tenant or an individual user.

When a user accesses the following url in 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 of this custom Web API application. Please change these values to meet your application’s settings.)

https://login.microsoftonline.com/common/oauth2/v2.0/authorize
  ?response_type=id_token
  &response_mode=form_post
  &client_id=f6da5452-7f05-4182-bd2d-feac1d2e86e2
  &scope=openid
  &redirect_uri=https%3A%2F%2Flocalhost%2Ftestapi
  &nonce=abcdef

Note : You can revoke the permission from :
https://account.activedirectory.windowsazure.com/, when you are using the organization account (Entra Account). 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 using a web client application.)

First register the new client application (which calls your custom Web API) in Entra ID management (on Azure Portal) as usual.
Now you generate client’s secret (app password) for this application.

Let’s start to consume the custom scope in your Web API using this client.
First, access the following URL with your web browser. (As you can see, the requesting scope is the previously registered custom scope api://f6da5452-7f05-4182-bd2d-feac1d2e86e2/read.)
Here client_id and redirect_uri are for the web client application (not custom Web API 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%2Ff6da5452-7f05-4182-bd2d-feac1d2e86e2%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 steps, because we focus on API scopes.

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

Once the login is succeeded 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 a previously registered custom scope (api://f6da5452-7f05-4182-bd2d-feac1d2e86e2/read).

After you approve this consent, the code will be returned into your redirect URL as query string.

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%2Ff6da5452-7f05-4182-bd2d-feac1d2e86e2%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://f6da5452-7f05-4182-bd2d-feac1d2e86e2/read",
  "expires_in": 3599,
  "ext_expires_in": 0,
  "access_token": "eyJ0eXAiOi..."
}

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

Using the returned access token (access_token property in above), you can call your custom Web API as follows and the API can verify the incoming 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 in turn for your custom Web API.

In your Web API, you should verify the access token (make sure that it isn’t tampered), check claims, and proceed the request.
For instance, if your API is an e-mail application, it might process :

  • Verify the access token
  • Check claims
    • The token timestamp is valid (start and expiration)
    • The scope is valid
    • The user has permission
      and so on …
  • Proceed the request
    • Retrieve user from claims
    • Get e-mail for that user
    • Return e-mail (Respond)

As I mentioned in “How to verify id token in Entra ID“, the access token is also JWT in organization account (see below), then you can process verification and claim extraction with the same steps as id token.
See “How to verify id token in Entra ID“.

id token access token
organization account (Entra ID) JWT JWT
consumer account (MSA) JWT Compact Tickets

JWT of access token includes the following claims.

{
  "aud": "f6da5452-7f05-4182-bd2d-feac1d2e86e2",
  "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"
}

Here I pick up some important claims :

  • aud means the application id for targeting web api (here, custom Web API).
  • nbf (= not before) is the starting time of the token, and exp is the expiration of the token.
  • tid means the tenant id of this logged-in user.
  • scp is the granted scopes.
  • oid is immutable user’s id. On the contrary, sub is a pairwise id for application and user. Hence, when the application changes, sub is also different. (oid is always same for a user.)

I show you PHP sample code for checking these claims.
Here I don’t go so far, but see “How to verify id token in Entra ID” for details.

<?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'], 'f6da5452-7f05-4182-bd2d-feac1d2e86e2') !== 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 (e.g, Microsoft Graph API, other custom APIs, and so on) ?

In such a case, your API can convert to another token with OAuth on-behalf-of flow as follows. In this flow, 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 other APIs except for “Microsoft Graph”, please see “Entra ID – How to use custom scopes for admin consent“. (Added 02/07/2018)

Note : For on-behalf-of flow in Azure AD v1 endpoint, please refer my early post .

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 Azure Portal. In this example, we add Mail.Read permission as follows. (When you use admin consent, you cannot add scopes on requesting on the fly. 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). f6da5452-7f05-4182-bd2d-feac1d2e86e2 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=f6da5452-7f05-4182-bd2d-feac1d2e86e2
  &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 Entra ID.

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 Entra ID endpoint using the passed access token. (I note that eyJ0eXAiOi... is the passed access token for this custom Web API, f6da5452-7f05-4182-bd2d-feac1d2e86e2 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=f6da5452-7f05-4182-bd2d-feac1d2e86e2
&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

 

Update History :

Dec 04, 2019  Update App Registration Portal (https://apps.dev.microsoft.com) to Azure Portal, because of App Registration Portal deprecation

 

Categories: Uncategorized

Tagged as: ,

33 replies »

  1. 1
    down vote
    favorite

    I’ve been playing around with API Management and it looks great. The only issue I have is that when calling an API you need to pass a subscription key, which is linked to a ‘User’. In the majority of use cases the caller of our APIs are Applications (back-end services).

    Am I supposed to be creating a User account per Application, or is there another way for me to obtain a security key for the App?

    Like

  2. Hi Tsuyoshi, I’m using azure ad b2c authentication flow with my php application. My problem is the azure ad JWT access tokens has x5c entry where B2C doesn’t have this entry. So how do I validate the azure ad b2c token with the digital signature. I did period check and audience check successfully.

    Like

  3. Hi Tsuyoshi-san. Thank you very much for your kind response. I fixed my issue. But I have a concern about the life time of the public key from from https://{issuer url}/.well-known/openid-configuration. Do we need to call it every time, does public key expire, is it valid to store public key in cache and reuse ?

    Like

  4. I was extremely pleased to uncover this web site. I wanted to thank you for
    ones time due to this wonderful read!! I definitely enjoyed every little bit of it
    and I have you bookmarked to look at new information on your website.

    Like

  5. This is the perfect blog for anyone who hopes to understand
    this topic. You understand so much its almost hard to
    argue with you (not that I really will need to…HaHa).
    You definitely put a new spin on a subject that’s been written about for decades.

    Excellent stuff, just great!

    Like

  6. I’m still learning from you, but I’m improving
    myself. I definitely enjoy reading everything
    that is written on your blog.Keep the aarticles coming.
    I loved it!

    Like

  7. I was wondering if you ever thought of changing the structure of
    your blog? Its very well written; I love what youve got to say.
    But maybe you could a little more in the way of content
    so people could connect with it better. Youve got an awful lot of text for only having 1 or 2 pictures.

    Maybe you could space it out better?

    Like

  8. What’s up mates, how is the whole thing,
    and what you want to say on the topic of this post, in my view its genuinely awesome in favor of me.

    Like

  9. of course like your web site however you have to check the spelling on several of
    your posts. Many of them are rife with spelling issues and I to find it very troublesome to
    inform the reality on the other hand I’ll definitely come back
    again.

    Like

  10. Добрый день! Кто-то из подписчиков моей страницы в MySpace подписан на автора этого веб-сайта и
    может перейти туда, чтобы посмотреть увлекательные
    материалы? Я оставил закладку и обязательно поделюсь с моими подписчиками!
    Отличный блог, гармоничный стиль и дизайн.

    Like

  11. Все любят вещи, которые вы,
    ребята, так мастерски делаете.
    Отличная работа и актуальные отчетности!
    Ваш блог потрясающий Я добавил адрес страницы в закладки!

    Ребята, вы настоящие профи, огромный респект!

    Like

  12. Вау! Я действительно наслаждаюсь скачивая
    шаблоны и темы этого сайта. Они просты, но эффективны для
    оформления. Часто тяжело найти “идеальный баланс” между комфортом использования и внешним видом ресурса.

    Я должен сказать, что этот человек проделал фантастическую работу над материалом.
    К тому же, процесс загрузки супер скоростной, у меня браузер Firefox.

    Выдающийся блог!

    Like

  13. Эй, приветик! Кто-то из подписчиков
    моей страницы в Твиттер подписан на автора этого блога и может перейти туда, чтобы оценить отличные
    материалы? Я сделал закладку и
    обязательно поделюсь с моими подписчиками!
    Замечательный блог, удивительный дизайн и стиль.

    Like

Leave a reply to Tsuyoshi Matsuzaki Cancel reply