Background

Microsoft Azure Active Directory supports an OAuth2 protocol extension called On-Behalf-Of flow (OBO flow). This is documented at both the Microsoft Identity Platform V1 and V2 endpoint. The OBO flow is used in the following scenario. Both Web API 1 and Web API 2 are protected by Azure AD.

  1. A client application (could be a SPA app, a front-end Web Application, or a native application) signs a user into Azure AD and request a delegated access token for Web API 1
  2. Client application then calls Web API 1 with the issued access token
  3. Web API 1 in turn needs to call a downstream Web API 2 so it uses its access token (in step 2 above) to request an access token for Web API 2. What happens in this step is that Web API 1 uses the OBO flow to exchange its access token for another resource’s access token. The exchanged token is still issued on behalf of the original sign in user and it has delegated permission.
  4. Web API 1 uses the new access token to call Web API 2

Let’s look at the parameters used in an OBO flow at the V1 endpoint below. I want to call out a few highlighted parameters as their significance will become more obvious a little bit later.

assertion:
this is the access token issued in step 2 above

client_id:
application id of Web API 1

Resource:
this is Application ID URI or Application ID of Web API 2

Let’s look at an OBO end to end traffic in Fiddler:

Frame 1 – 14 below shows the user navigates to the web site and is redirected to Azure AD to log in. Frame 15 is the request to the token endpoint to get an access token for Web API 1

Hover over image to enlarge

In this example Web API 2 is Microsoft Graph. In frame 19 below Web API 1 uses an OBO flow to request a token for Microsoft Graph. It uses the access token received in frame 15 as the assertion parameter.

Hover over image to enlarge

It is extremely important to use the correct parameter in the OBO flow. Note that the OBO parameters client_id and the assertion (access token) are for the calling application (Web API 1) in this token exchange request.

Common pitfall customers run into when using the OBO flow

I have seen a few AADSTS error returned for this flow when either the client_id or the assertion parameters used are not for the calling application (Web API 1). Below are a few examples:

HTTP 400 error: AADSTS500131: Assertion audience does not match the Client app presenting the assertion. The audience in the assertion was ‘00000002-0000-0000-c000-000000000000’ and the expected audience is …

Root cause: The access token used in the assertion is for a different application / resource instead of for the calling app Web API 1. The GUID in this error is an Azure AD Graph resource.

HTTP 400 error: AADSTS50013: Assertion failed signature validation. [Reason – The provided signature value did not match the expected signature value., Thumbprint of key used by client:…

Root cause: The access token used in the assertion is for Microsoft Graph resource (https://graph.microsoft.com)

HTTP 400 error: AADSTS50013: Assertion failed signature validation. [Reason – The key was not found., Thumbprint of key used by client: ‘B25930C…..
Root cause: Web API 1 is a SAML Application (check the Enterprise Application blade to see if Single sign-on is enabled and there is a SAML signing Certificate attached).

HTTP 500 error: AADSTS50000: There was an error issuing a token.

Root cause: the client id used is either not valid or does not exist in the tenant.

How can I diagnose this issue?

  • Take a Fiddler trace to see what the parameters used are.
  • Use https://jwt.ms to decode the access token assertion and look at the “aud” (audience) claim to see if it’s for the calling web API 1

What if my Web API 2 is SAML Application?

If the downstream API App can only consume SAML token (instead of jwt token), you can certainly use the OBO flow to exchange a JWT token for the SAML token using the following parameters (see https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-on-behalf-of-flow for more info). The key is in the parameter requested_token_type. The allowed values are

urn:ietf:params:oauth:token-type:saml2 – SAML 2.0 token

urn:ietf:params:oauth:token-type:saml1 – SAML 1.1 token

Note:
At the time of this writing, this JWT – SAML token exchange OBO flow is only available in the V1 endpoint.

Note: Azure AD returns the SAML token encoded in UTF8 and Base64URL as noted in the documentation

What about Azure AD B2C?

The OBO flow is currently not supported in Azure AD B2C per Application types that can be used in Active Directory B2C

Drop me a comment if you find this useful or any other important information I should add.

==========

Update 8/22 – added one more condition for error AADSTS50013 due to SAML App

32 Thoughts to “Understanding Azure AD’s On-Behalf-Of flow (aka OBO flow)”

  1. MARCIO FELIPE LAUBSTEIN

    Great article, I am trying to get a saml token from a jwt token but when i hit the endpoint to get the saml token i am receiving the following error:

    {
    “error”: “invalid_grant”,
    “error_description”: “AADSTS50013: Assertion failed signature validation. [Reason – The key was not found., Thumbprint of key used by client: ‘CAC25D36924A3AD5B1D66E884AA55B5F04526F1A’]\r\nTrace ID: 47644c95-3bd8-46c3-b320-23fbe9432c00\r\nCorrelation ID: 5e83afde-3791-4d87-bce2-dfe5dc5d4ee6\r\nTimestamp: 2019-08-21 00:33:27Z”,
    “error_codes”: [
    50013
    ],
    “timestamp”: “2019-08-21 00:33:27Z”,
    “trace_id”: “47644c95-3bd8-46c3-b320-23fbe9432c00”,
    “correlation_id”: “5e83afde-3791-4d87-bce2-dfe5dc5d4ee6”
    }

    I see you did not mention about certificates, Am I missing something?

    Thanks!

    1. MARCIO FELIPE LAUBSTEIN

      My downstream api is configured with saml sso, i do not know if that could be the issue.

      1. Bac Hoang [MSFT]

        Hi Marcio,
        Check your Web API 1 (the one that requests an access token for web API 2 using OBO flow) to make sure it’s an OAuth2 application and not a SAML application. Is Web API 1 created in App Registration blade or Enterprise Application blade? If created in the Enterprise Application blade then there is a good chance it’s a SAML application (The single Sign On blade is enabled and there is a SAML certificate attached). In general we don’t recommend the same application being used as a SAML app and an OAuth2 app. That can create problem due to different maintenance of signing certificates. Please open a support ticket with us if this does not help. Your downstream web API 2 configured with SAML SSO should be fine.

        1. Márcio Masculino Laubstein

          Thanks a lot Bac, indeed I had created the Web API 1 application using the Enterprise blade, I thought it was right because the single sign on were disabled but after creating it through app registration blade it worked.
          If my two applications were SAML can I use OBO flow the same way or I need to have a OAuth2 token to start with? Is it possible to bypass the problems of signing certificates if my WEB API 1 were SAML and OAuth2 at the same time?

          Thanks again 🙂

          1. Bac Hoang [MSFT]

            Unfortunately, web API 1 has to be an OAuth2 application for this to work. Your Web API 2 can be a SAML App. Thanks for bringing this issue to my attention. I updated the post to include this scenario now.

        2. Márcio Masculino Laubstein

          I received a saml token to my downstream API but I tried to validate it by just decoding and inflating the xml and it is not a valid saml token, maybe is it encoded in a different way?

          Thank you!

          The response:

          {
          “token_type”: “Bearer”,
          “expires_in”: “3065”,
          “ext_expires_in”: “3065”,
          “expires_on”: “1566416697”,
          “resource”: “my resource”,
          “access_token”: “PEFzc2VydGlvbiBJRD0iXzFhMTQyMDIyLTY0NjktNDMzYy04NzIzLThmMzkxNzEzM2IwMCIgSXNzdWVJbnN0YW50PSIyMDE5LTA4LTIxVDE4OjUzOjUyLjI2MloiIFZlcnNpb249IjIuMCIgeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPjxJc3N1ZXI-aHR0cHM6Ly9zdHMud2luZG93cy5uZXQvOGRhNTIzNGUtYWU4ZS00MWNkLTlmM2UtZGFiOTBhZGE0YWFhLzwvSXNzdWVyPjxTaWduYXR1cmUgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxTaWduZWRJbmZvPjxDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8-PFNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiLz48UmVmZXJlbmNlIFVSST0iI18xYTE0MjAyMi02NDY5LTQzM2MtODcyMy04ZjM5MTcxMzNiMDAiPjxUcmFuc2Zvcm1zPjxUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L1RyYW5zZm9ybXM-PERpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIvPjxEaWdlc3RWYWx1ZT5yK1BLS2xxZWJRMG8wMTlOSW5QUHIzK2JtVTFIa0E2ZGN2eHMxZ1o2SWo4PTwvRGlnZXN0VmFsdWU-PC9SZWZlcmVuY2U-PC9TaWduZWRJbmZvPjxTaWduYXR1cmVWYWx1ZT5jT2VXYWFYVWJ2TEswQzFpZU1CODBWWVYxcFRnVnZIUEowUklRWWNOcmtTWWlCU2hMRzJCWHJ6ajVYREJ3YmdZaC9kd1paZ01iVFJ6Uk1McUtuTVMyTWtSMzFDMmdyRUREUE1BeUFZWDIxbTJlak9uSHdpdDhHRUtud3Q5VlZUcnFCYWh1eWRUa3lYQTFEWlZ4ZVk4S0FnQXAwRko5TUk0cUZGTklTYmxvWVZKV1Q3MUt4MHlBbU93OTl4ZGhJci9yWkc5ZmZUc0ZsemNIRFNNclJyOWc5NE1YK2hGRzMxcmd0NWkyOE9qQW1UbVlUNEIzM1ZuYkpraUI1Slc4cDI2aW5Qem9EbVdGS0paaGJ4MVExaXU3cklqeFpsc0hXNldjM3JnRG5FdHk2NWJxL2lFOFpyNGRDVFNERWxWVXdqUkkrOE5nb1ZLT3RONTlISXJIL2RXUUE9PTwvU2lnbmF0dXJlVmFsdWU-PEtleUluZm8-PFg1MDlEYXRhPjxYNTA5Q2VydGlmaWNhdGU-TUlJQzhEQ0NBZGlnQXdJQkFnSVFXZ2JwcnZ5enViOU8zK0RQdTEvMThEQU5CZ2txaGtpRzl3MEJBUXNGQURBME1USXdNQVlEVlFRREV5bE5hV055YjNOdlpuUWdRWHAxY21VZ1JtVmtaWEpoZEdWa0lGTlRUeUJEWlhKMGFXWnBZMkYwWlRBZUZ3MHhPVEEyTWpneE9ETXlNalZhRncweU1qQTJNamd4T0RNeU1qVmFNRFF4TWpBd0JnTlZCQU1US1UxcFkzSnZjMjltZENCQmVuVnlaU0JHWldSbGNtRjBaV1FnVTFOUElFTmxjblJwWm1sallYUmxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXBMdGVwQjBxamtPb2YrY2NuK2RHV05wZDJicjVMT1ZjSnExT1oyNy92OWZhZlE2U3djUFdHMWpnMDEzdmtxUDZCSTdtYmZlUTMvcFQ3ajA2NUdlMUQ3VWZ5MlM2eitTNzN1dFV4b29BamZ1cEJyS2ZpWHNSWG5ORFZ1UjhtenFWUm1EMXdKajVsVndJMkJ0ZkdFNWh2THNBUmd2d2xFUk5pTVpaUCs2dXE0QVFsQjFxUHlSUmN0cEZoZEtpQml5eHJhdm5wTDBDMHdPcmYrYzIyQllJTEJFbVRyclpoZUpjMXZyUzBleC9vTWNRSDZteVNDVXg2U1ZLeVdmcktReUNPMzNRSXZsMHkwL1p5Tm5KaHdDa2FuOVV6RjRZTi9GQXdmMHNzb0d3SG13UFNhbzNYS08xYnZuZHJ5bUVVYURkNmkzSnpVQnJvOFBtVERSYWIzbHI0UUlEQVFBQk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQXdraW94SHZsWExjRmpHZFFFeGlSMk1FSnIwSlordGxYODAzRnFOR1p3clB6ME5LTVVINmE0WHR6c3JSTzR3b1FvQ3JYWk1jeUdPRmIzM2lCaXNWS3pvUWczbDhZalltRFRWMGRVYUhOak1XQlM1ckZKUlFiL2JWbzNZRzB4NXJyaWlHS3FNMndRK1djYmt0TW8xYUtzV1A1cWNMcXBOWEI2dDRYM3pOWm9sZ0ErT2RqcDRYTzBhOHhhRXIvZkVpRU0zRUlLYkl4am81SVBuNk9BQUo1cEVyaTFyMUtoZ1ZMNHNjK3FvZlh4bytPRnhVT0lmU3ZyVmt3THJmcDVsQ3dKYm0rKzRId3RZaXFCYW93SXQ0bTkvY0NMclJYOGYyM0FhMGtGTEd0dmFvd1BYQWpFOW03VEVmM3dHcnpyUDBzVDFtSnl2RVZibE9SaHBTUVp4ZytvPC9YNTA5Q2VydGlmaWNhdGU-PC9YNTA5RGF0YT48L0tleUluZm8-PC9TaWduYXR1cmU-PFN1YmplY3Q-PE5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI-anVyaXRpcy5sZWdhbGRlc2tAbWFjaGFkb21leWVyLmNvbS5icjwvTmFtZUlEPjxTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI-PFN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAxOS0wOC0yMVQxODo1ODo1Mi4yNjJaIiBSZWNpcGllbnQ9Imh0dHBzOi8vZ2VkMXNwLm1tc28uY29tLmJyL2FwaS92MS9zZXNzaW9uL3NhbWwtbG9naW4iLz48L1N1YmplY3RDb25maXJtYXRpb24-PC9TdWJqZWN0PjxDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxOS0wOC0yMVQxODo0ODo1Mi4yNjJaIiBOb3RPbk9yQWZ0ZXI9IjIwMTktMDgtMjFUMTk6NDQ6NTcuMjYyWiI-PEF1ZGllbmNlUmVzdHJpY3Rpb24-PEF1ZGllbmNlPnNwbjo2OGI0YjNiMy05NTNiLTRiY2EtYjFiMC00NzY1YTAzMWRlMzA8L0F1ZGllbmNlPjwvQXVkaWVuY2VSZXN0cmljdGlvbj48L0NvbmRpdGlvbnM-PEF0dHJpYnV0ZVN0YXRlbWVudD48QXR0cmlidXRlIE5hbWU9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vaWRlbnRpdHkvY2xhaW1zL3RlbmFudGlkIj48QXR0cmlidXRlVmFsdWU-OGRhNTIzNGUtYWU4ZS00MWNkLTlmM2UtZGFiOTBhZGE0YWFhPC9BdHRyaWJ1dGVWYWx1ZT48L0F0dHJpYnV0ZT48QXR0cmlidXRlIE5hbWU9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vaWRlbnRpdHkvY2xhaW1zL29iamVjdGlkZW50aWZpZXIiPjxBdHRyaWJ1dGVWYWx1ZT40NmQwMDc3OC1kMmRiLTRmYWQtYWNlMy05NmI1N2I0MDlkNDc8L0F0dHJpYnV0ZVZhbHVlPjwvQXR0cmlidXRlPjxBdHRyaWJ1dGUgTmFtZT0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9pZGVudGl0eS9jbGFpbXMvZGlzcGxheW5hbWUiPjxBdHRyaWJ1dGVWYWx1ZT5qdXJpdGlzLmxlZ2FsZGVzazwvQXR0cmlidXRlVmFsdWU-PC9BdHRyaWJ1dGU-PEF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL2lkZW50aXR5L2NsYWltcy9pZGVudGl0eXByb3ZpZGVyIj48QXR0cmlidXRlVmFsdWU-aHR0cHM6Ly9zdHMud2luZG93cy5uZXQvOGRhNTIzNGUtYWU4ZS00MWNkLTlmM2UtZGFiOTBhZGE0YWFhLzwvQXR0cmlidXRlVmFsdWU-PC9BdHRyaWJ1dGU-PEF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL2NsYWltcy9hdXRobm1ldGhvZHNyZWZlcmVuY2VzIj48QXR0cmlidXRlVmFsdWU-aHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2F1dGhlbnRpY2F0aW9ubWV0aG9kL3Bhc3N3b3JkPC9BdHRyaWJ1dGVWYWx1ZT48L0F0dHJpYnV0ZT48QXR0cmlidXRlIE5hbWU9Imh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2dpdmVubmFtZSI-PEF0dHJpYnV0ZVZhbHVlPmp1cml0aXMubGVnYWxkZXNrPC9BdHRyaWJ1dGVWYWx1ZT48L0F0dHJpYnV0ZT48QXR0cmlidXRlIE5hbWU9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvd2luZG93c2FjY291bnRuYW1lL1NBTS1BY2NvdW50LU5hbWUiPjxBdHRyaWJ1dGVWYWx1ZT5qdXJpdGlzLmxlZ2FsZGVzazwvQXR0cmlidXRlVmFsdWU-PC9BdHRyaWJ1dGU-PEF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIj48QXR0cmlidXRlVmFsdWU-anVyaXRpcy5sZWdhbGRlc2s8L0F0dHJpYnV0ZVZhbHVlPjwvQXR0cmlidXRlPjwvQXR0cmlidXRlU3RhdGVtZW50PjxBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTktMDgtMjFUMTc6MTE6MjguOTk0WiI-PEF1dGhuQ29udGV4dD48QXV0aG5Db250ZXh0Q2xhc3NSZWY-dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L0F1dGhuQ29udGV4dENsYXNzUmVmPjwvQXV0aG5Db250ZXh0PjwvQXV0aG5TdGF0ZW1lbnQ-PC9Bc3NlcnRpb24-“,
          “issued_token_type”: “urn:ietf:params:oauth:token-type:saml2”,
          “refresh_token”: “AQABAAAAAAAP0wLlqdLVToOpA4kwzSnx7OxJRgLX-UAhKbYaMoYckn7qM6CdFEgsuphE1n_0rlyym3W5EjSo_bslS2QN9EWtIzM-3Im8T3XHeyHtEsim7xkvmixDRUXDt65bAC3sHIR1lT1IWOmuWrAPF1Lzv6BRYa9gIilwmaU4JZLcZPvp8nN48f6Uhm8T9YDOmrIXRqBxktSAOZOkeivD7mOwiVqAAcsl9Z_mcxGroYjP4m1p8_Xq-00qgUsBzTEm6olUsFYoAeQsigayPPDmDs6OpiOJk976pXvjvWVYys8pz1qokm95P8L8r01ofK71u6NT2sSbe86REVwZZCLwuivKpHpKrUEK2bHvsKMI-dH6hkoa2I21_7njqtuSXnBFYbz-zKVIcuRgBtOR0xkDMUX3B6LitHxgtSjev9g2_Jw5-d6WTFKPY3B2YueGYFvHtbY7hwnaQaIqgXy_f5CFUyGu_ECMlmIsuaKkRKKNkWpct6Cktb7Pf3LSzywl7r8C4pItV822xytE0msAaLdZDR0MBom-omr_aivNQee_ulLvoZYC2zMXwv4OUdO87umu0unp_50s6GBmwQzv2XcIQQ_Pt8PrIs-sczWd6R7B8vgww1SSIv8IGEGMu8XLFUOYLTJNmivbsvlslD6ybaHEDMSlZ4I6QAqvg_z6LacYGsYU0eslScDd1JArZb4CUeik8c6xPoFi5p9dxmlxWd2fxuG40_9b3Fbelz5t5uS0NEbJLrqC-gWYhApfrW6KxXilnlRTqrw4-pFnuC92AZMnailejVkLIAA”,
          “id_token”: “eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiI0OTU0YTFjYy1hZTU0LTQ2YzAtYmExZS02NmNmN2E3NjY2NzMiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC84ZGE1MjM0ZS1hZThlLTQxY2QtOWYzZS1kYWI5MGFkYTRhYWEvIiwiaWF0IjoxNTY2NDEzMzMyLCJuYmYiOjE1NjY0MTMzMzIsImV4cCI6MTU2NjQxNjY5NywiYW1yIjpbInB3ZCJdLCJnaXZlbl9uYW1lIjoianVyaXRpcy5sZWdhbGRlc2siLCJpcGFkZHIiOiIxNzcuMTAzLjIyMy4zNyIsIm5hbWUiOiJqdXJpdGlzLmxlZ2FsZGVzayIsIm9pZCI6IjQ2ZDAwNzc4LWQyZGItNGZhZC1hY2UzLTk2YjU3YjQwOWQ0NyIsIm9ucHJlbV9zaWQiOiJTLTEtNS0yMS0yMDA2Njc2NDE3LTE5MTM5ODEwMjQtMTg4NTYyNTE1Ni0zODQ0MCIsInN1YiI6ImdMQ0Rkc1g2Z1FRTC1SeVpWU09GczRBZ1dfTDVHQnZLcXMxSjlXWm1reUEiLCJ0aWQiOiI4ZGE1MjM0ZS1hZThlLTQxY2QtOWYzZS1kYWI5MGFkYTRhYWEiLCJ1bmlxdWVfbmFtZSI6Imp1cml0aXMubGVnYWxkZXNrQG1hY2hhZG9tZXllci5jb20uYnIiLCJ1cG4iOiJqdXJpdGlzLmxlZ2FsZGVza0BtYWNoYWRvbWV5ZXIuY29tLmJyIiwidmVyIjoiMS4wIn0.”
          }

          1. Bac Hoang [MSFT]

            Your access token is a Base 64-encoded SAML token. Just run it through any Base64 decoder and you should see the XML string.

          2. Márcio Masculino Laubstein

            Thanks Bac for spending your time helping me! I already decoded and inflated the SAML token above, but it seems to be encoded/encrypted as follows:

            <Issuer΋���˝�[���˛�]�
            YY
            �YYKM�XKM�KXLLL�X�M�̌�
            ��K��\��Y\���Yۘ]\�H[��H������˝�˛ܙ�̌�K�[�Y�ȏ��YۙY[��Ϗ�[�ۚX�[^�][ۓY]�[�ܚ]OH������˝�˛ܙ�̌K�L�[Y^�X�M�ȋ��6�v�GW&T�WF��B�v�&�F���&�GG���wwr�s2��&r�#�B�� …. (so on)

            It is always the first child node that is being encrypted, I have tried many times and all my received SAML tokens are this way, do you have an ideia what this could be?

            Thanks!

          3. Márcio Masculino Laubstein

            The Base64 decoder says: The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.

          4. Bac Hoang [MSFT]

            I used Fiddler’s TextWizard feature (Transform = From Base64) and the tool at https://www.freeformatter.com/base64-encoder.html Both decode your token just fine.

          5. Márcio Laubstein

            Hi Bac, I was being fooled by the others decoder64 sites I visited, I had tried by example these two sites: https://www.base64decode.org/ https://codebeautify.org/base64-decode and both of them gave me these wrong results but indeed in Fiddler it worked.

            Just to reminder someone that is trying to do the same thing, the SAML token that is returned is an Assertion, it does not contain the SAML Response tag, so if your downstream API is expecting this tag you must decode the SAML token and wrap it with the SAML response tag and its status tag, after that you encode in base64 again and it is ready to be used.

            Thanks for your help. 🙂

          6. Márcio Laubstein

            For those who are trying to decode the SAML token response:

            About the decoding code, most of the tools to decode base64 follows the RFC 4648 (previously, RFC 3548; standard) but there is no agreement about some encondig characters, more specifically the 62nd (+) and 63rd (/), the response SAML token from the OBO flow returns a base64url even if the data comes from the request’s body, this way the (+) character is replaced by (-) and etc…

            Doing this in C# code won’t work: byte[] data = Convert.FromBase64(customBase64);

            The correct way is to use the HttpServerUtility.UrlTokenDecode method, it expects that at the end of the data it contains the number of paddings.

            The right code:

            if (base64String.Length % 4 != 0) base64String += (4 – base64String.Length % 4);
            else base64String += 0;
            var data = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(base64String));

          7. Bac Hoang [MSFT]

            Thanks for sharing Marcio

  2. Nikhil

    Hi, We are making an application using Azure Bot Service. Bot service internally calls one of our Web Service to get data based on query. We can consider Bot service as service1 and our Web Service as service2. After we get token from service1 we use that token as assertion to get bearer token from service2 by providing all required input parameters. We are getting error “AADSTS50013: Assertion failed signature validation. [Reason – The provided signature value did not match the expected signature value., Thumbprint of key used by client:…” and we cannot move forward. Can you please suggest what may be point of mistake?

    Thanks
    Nikhil

    1. Bac Hoang [MSFT]

      your error is listed above. Make sure you are using the correct access token for web service 1. I would suggest examining the access token using https://jwt.ms to verify the token used by service 1 in the OBO flow is meant for service 1 and not for another resource app.

  3. Hao Han

    Hi Bac Hoang,

    Does AAD support SAML Enhanced Client or Proxy Profile? I am able to get the SAML Assertion using OBO flow however I’m not sure how to wrap that up in a SAML Response. Wondered if I should use ECP instead. Thanks!

  4. Steve Kollmorgen

    Note:
    At the time of this writing, this JWT – SAML token exchange OBO flow is only available in the V1 endpoint.

    Can seem to get this to work for V2.0 Oauth enpoint. Do you know if that should work?

    1. Bac Hoang [MSFT]

      As far as I know, the JWT – SAML token exchange functionality is only available in the V1 endpoint.

  5. Stu Coe

    Hi, many thanks for this post. I’m working on an OBO flow but seem to be facing an issue (likely myself) with the aud claim in the originating service’s access token which is being sent to azure in the on_behalf_of POST request. I’m using the v1 endpoint.

    I have configured the aud claim as guid within the access token claim but it’s still coming through with a default(?) value of 00000002-0000-0000-c000-000000000000. Even before adding the claim, I was getting this value which seems to contradict the docs?

    Have I misunderstood how to configure the OBO flow? I am getting the AADSTS500131 error. I’m led to believe I’m attempting the correct fix as the error message tells me the aud claim should equal the value of the client_id.

    Any help is greatly appreciated!

    1. Bac Hoang [MSFT]

      That GUID is for Azure Active Directory Graph. Make sure you specify the correct resource when asking for your web API Access Token. If you still need help, please open a support case with us

  6. Vasanth Kumar M Y

    Hi
    I have dameon app need to send messages to team cannel, but currently application type doesn’t have permisson to send messages to channel.. is it possible to achive this using OBO flow. i have two apps registered one is for dameon and web api with exposed api.. where i can will authenticate to web api using client credentials from dameon app and use that assertion token to call the graph api with web api credentails..

    I am getting below error : ‘error’: ‘unauthorized_client’, ‘error_description’: “AADSTS7000114: Application ‘xxxxxxxxxxx’ is not allowed to make application on-behalf-of calls’

    1. Bac Hoang [MSFT]

      On Behalf Of (OBO) flow is only applicable for delegated permission where there is an actual user signing in. This does not work for application permission scenario where you are authenticating with a client id and secret.

      1. Vasanth Kumar M Y

        Thanks, could you please let me know how can i post messages to team channels form application. webhook or card connectors is not a option as channels are created dynamically from application

        1. Bac Hoang [MSFT]

          That discussion does not refer to using client credentials grant flow to get a token and then subsequently use that token in the OBO flow. There is a user context discussed there. Regarding your questions about Teams messages, it’s best if you can open a support case for the right team to assist you with.

          1. Vasanth Kumar M Y

            Thank you for the response

  7. Leonid Sokolovsky

    Using API gateway in front of back-end services is a typical approach used in a microservices architecture. OBO is a good way to implement it but as we know it this flow is still not supported in Azure AD B2C. Could you please suggest how we should address it?

    For API Gateway we are using lightweight Ocelot Gateway

    1. Bac Hoang [MSFT]

      Yes I believe your understanding is correct. OBO flow is currently not supported for Azure AD B2C

  8. James Fleming

    Thank you for sharing, I’m just getting started with the OBO samples on Github, how about some example code of calling APIs other than Graph? like Azure DevOps, etc.?

    1. Bac Hoang [MSFT]

      Hi James,
      The code is essentially the same. Instead of using Microsoft Graph scope, you can use Azure DevOps scope.

  9. Dalit

    Hi Bac,
    Thanks for this great post. I was wondering, do you know if the second request of an access token in the flow (the one by Web API 1 to access Web API 2) get logged in any Azure Active Directory sign in logs?
    Thanks!

    1. Bac Hoang [MSFT]

      Interesing…I am not sure. If you don’t find it in the sign-ins blade in the portal then perhaps it’s not logged. That 2nd request is not really a sign in request. It’s a token exchange request. Perhaps you can log a feedback item for a feature request here: https://feedback.azure.com/d365community/forum/22920db1-ad25-ec11-b6e6-000d3a4f0789

Leave a Comment