This brief is to describe how to leverage SAP BTP destination service to implement service to service calls from a SAP BTP hosted service (backend or frontend) against Microsoft Azure hosted resource (like MS Graph for instance), with or without a business user context. Disclaimer:
|
Terminology and security concepts refresher:
Methodology explainer:
|
Microsoft identity platform OAuth 2.0 On-Behalf-Of flow , known as OBO, allows exchanging a client application's id_token
that has a named user context against a bearer access_token
of a resource. More details on how to fetch this user JWT in appendix below,
urn:ietf:params:oauth:grant-type:jwt-bearer
grant type and thus is very much alike the OAuth2JWTBearer authentication flow offered by the destination service.X-user-token
header of the Find Destination call..X-user-token
header method this works the either way.id_token
that has the user context (email
claim) of a client application. The id_token audience (aud
claim below) - is the client id of the OIDC application.{
"typ": "JWT",
"alg": "RS256",
"kid": "<kid>"
}.{
"aud": "5a945db3-f165-4b7b-abbd-xxxxxxxxxxxx",
"iss": "https://login.microsoftonline.com/<tenant_id>/v2.0",
"iat": 1663452399,
"nbf": 1663452399,
"exp": 1663456299,
"aio": "<aio>",
"email": "foo.bar@microsoft.com",
"idp": "https://sts.windows.net/42f7676c-f455-423c-82f6-xxxxxxxxxxxx/",
"rh": "<rh>",
"sub": "<sub>",
"tid": "<tenant_id>",
"uti": "<uti>",
"ver": "2.0"
}.[Signature]
{
"owner": {
"SubaccountId": "<SubaccountId>",
"InstanceId": null
},
"destinationConfiguration": {
"Name": "Quovadis-AzureAD-JWT-ppm",
"Type": "HTTP",
"URL": "https://graph.microsoft.com/v1.0/me:443",
"Authentication": "OAuth2JWTBearer",
"ProxyType": "Internet",
"tokenServiceURLType": "Dedicated",
"tokenService.KeyStorePassword": "<KeyStorePassword>",
"clientId": "5a945db3-f165-4b7b-abbd-xxxxxxxxxxxx",
"Description": "https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow",
"tokenService.body.client_assertion": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsIng1dCI6IkRRTU9kcjlJVFp4aGQ2TllJN2w4eTZMSWRUdz0ifQ.eyJpc3MiO(truncated)H7XLf1EgHuWHw9Y5cGekyS8A_ZPIVM",
"scope": "openid email offline_access https://graph.microsoft.com/.default",
"x_user_token.jwks_uri": "https://login.microsoftonline.com/<azureAD_teant_id>/discovery/v2.0/keys",
"tokenService.body.assertion": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSJ9.eyJhdWQiOiI(truncated)th7ougSpgvkKaAxA6lOB60PVQEj7LAdqaUqJ8MN36rBXYUJnaN3sgJbrlbQDjNE2FP9sw",
"tokenServiceURL": "https://login.microsoftonline.com/958a12e6-de37-4185-8ea2-c3f59ed0f97f/oauth2/v2.0/token",
"tokenService.KeyStoreLocation": "<KeyStoreLocation>",
"tokenService.body.requested_token_use": "on_behalf_of",
"tokenService.body.client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
},
"authTokens": [
{
"type": "Bearer",
"value": "eyJ0eXAiOiJKV1QiLCJub25jZSI6IkJubGc1eWJMM29RS0xtcnAwLWVnLTN3dVZHQktlVUNIUGRvSE9FTDVpaWsiLCJhbGci(truncated)yfAU2hXe3xNU2mo7GGjIHpOZugwacgoRQ040RuTf8wBr-aHpANUXPvx6-xp05dYSkvf6XDzjOAd9cJg0A4R6PLFGVSOnQ",
"http_header": {
"key": "Authorization",
"value": "Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6IkJubGc1eWJMM29RS0xtcnAwLWVnLTN3dVZHQktlVUNIUGRvSE9FTDVpaWsiLCJhbGciO(truncated)yfAU2hXe3xNU2mo7GGjIHpOZugwacgoRQ040RuTf8wBr-aHpANUXPvx6-xp05dYSkvf6XDzjOAd9cJg0A4R6PLFGVSOnQ"
},
"expires_in": "1613",
"scope": "email openid profile https://graph.microsoft.com/Calendars.Read https://graph.microsoft.com/Calendars.Read.Shared https://graph.microsoft.com/Calendars.ReadWrite https://graph.microsoft.com/Calendars.ReadWrite.Shared https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/Mail.Read.Shared https://graph.microsoft.com/Mail.ReadBasic https://graph.microsoft.com/Mail.ReadWrite https://graph.microsoft.com/Mail.ReadWrite.Shared https://graph.microsoft.com/Mail.Send https://graph.microsoft.com/Mail.Send.Shared https://graph.microsoft.com/Tasks.Read https://graph.microsoft.com/Tasks.Read.Shared https://graph.microsoft.com/Tasks.ReadWrite https://graph.microsoft.com/Tasks.ReadWrite.Shared https://graph.microsoft.com/User.Read https://graph.microsoft.com/.default",
"refresh_token": "0.AVkA5hKKlTfehUGOosP1ntD5f7NdlFpl8XtLq73n19NtGyNZAJo.AgABAAEAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P9qaIHJUpAw6SAIk02ESRo4DTgdzWFRBEbqAeS1BjSyhN5UYYXUtbXs7rU-PmqJQDKv8Ovq_T4Qvl_gmPNia6R2-wPwthKp4TWx6SnHoaBcSrqGjgO0CGIsYZs_Jb2QA-joU93r4mxa83bT8WRrweS53FQGrS2ACCa_FazILmSRSz6MtTbMEycELsG_ou1U7VldFssSNXKWpaY1k9CWWGEunLVmODHujbjpT9FD1WH7f__sjj2WVtle19WvxoxJngsuKU0Oj-mj-a2NJw_mMweQ14nW7S9E93OE6kQWRDuk3mPqLLq29PUp-0-ogU1xfIV26myC_EjZ16DWbqyytQZdPG77iZjtl0_v-nEz3iAff0JFE34NyBhMJlTCwQRkFH0qx12mGXJDs7iPITuxA6X_CLkyug22FY_7RPRQgrBjwS_4PkXppu85HZJoCCp_cnOHzICS6D621gTCyFz0vUQy8AyE9vdcTybkkI2MnXnavHKsEeVZ-OuAfCyfPkgHOd2LbHm73KCd0mFDpnQMfK1rOMnoaVa5dQudOtjWl2KZAvK6d_MyGxEN-R8O-Ac2-XT3i2PQ4oK0iJb0S0qNmBP6LuOwwtDiCFUgyZ1-LjFCZzyMQ0W92DqO0OPIZKdjywyWao-Tu7vynS6Rbs7NOmryx03_kheAcJqahL9aZg7VTqQG_YEF0kaw7GxHDORmNo3-5fw007xbXBQnjxIyUFd93xiIWbaMwneiojXg8CFEOvHbuMfMqy9AdTLyStvpx_3WKVSGQQT7-nOoov3BzoyO7pNmrGUI7Dw6iRtiTgc5STvPlw32wNo1gFhveEJ4qX8enW7xVEYBsFL7tDraKmM21eA"
}
]
}
{
"typ": "JWT",
"nonce": "<nonce>",
"alg": "RS256",
"x5t": "<x5t>",
"kid": "<kid>"
}.{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/<tenant_id>/",
"iat": 1663620945,
"nbf": 1663620945,
"exp": 1663624653,
"acct": 0,
"acr": "0",
"aio": "<aio>",
"altsecid": "5::10037FFE94410318",
"amr": [
"rsa",
"mfa"
],
"app_displayname": "Quovadis-SAP",
"appid": "5a945db3-f165-4b7b-abbd-xxxxxxxxxxxx",
"appidacr": "2",
"email": "foo.bar@microsoft.com",
"family_name": "Bar",
"given_name": "Foo",
"idp": "https://sts.windows.net/42f7676c-f455-423c-82f6-xxxxxxxxxxxx/",
"idtyp": "user",
"ipaddr": "<ipaddr>",
"name": "LUC-ISVENG",
"oid": "<oid>",
"platf": "5",
"puid": "<puid>",
"rh": "<rh>",
"scp": "Calendars.Read Calendars.Read.Shared Calendars.ReadWrite Calendars.ReadWrite.Shared email Mail.Read Mail.Read.Shared Mail.ReadBasic Mail.ReadWrite Mail.ReadWrite.Shared Mail.Send Mail.Send.Shared openid profile Tasks.Read Tasks.Read.Shared Tasks.ReadWrite Tasks.ReadWrite.Shared User.Read",
"sub": "<sub>",
"tenant_region_scope": "NA",
"tid": "<tenant_id>",
"unique_name": "foo.bar@microsoft.com",
"uti": "<uti>",
"ver": "1.0",
"wids": [
"62e90394-69f5-4237-9190-012177145e10",
"b79fbf4d-3ef9-4689-8143-76b194e85509"
],
"xms_st": {
"sub": "<sub>"
},
"xms_tcdt": <xms_tcdt>
}.[Signature]
curl https://graph.microsoft.com/v1.0/me -H "Authorization: Bearer <access_token>"
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
"businessPhones": [],
"displayName": "LUC-ISVENG",
"givenName": "Foo",
"jobTitle": null,
"mail": null,
"mobilePhone": null,
"officeLocation": null,
"preferredLanguage": null,
"surname": "Bar",
"userPrincipalName": "foo.bar_sap.com#EXT#@ppmxxxxxxx.onmicrosoft.com",
"id": "<id>"
}
{
"owner": {
"SubaccountId": "<SubaccountId>",
"InstanceId": null
},
"destinationConfiguration": {
"Name": "Quovadis-AzureAD-clientSecret",
"Type": "HTTP",
"URL": "https://graph.microsoft.com/v1.0/$metadata:443",
"Authentication": "OAuth2ClientCredentials",
"ProxyType": "Internet",
"tokenServiceURLType": "Dedicated",
"tokenService.KeyStorePassword": "<KeyStorePassword for mTLS communication>",
"clientId": "8e4ed817-c1b0-4cab-89a0-3bbfa1234567",
"Description": "OAuth2ClientCredentials",
"scope": "openid email offline_access https://graph.microsoft.com/.default",
"clientSecret": "c91z***********************",
"tokenServiceURL": "https://login.microsoftonline.com/3e2cfdd6-0dc4-4387-a7cf-xxxxxxxxxxxx/oauth2/v2.0/token",
"tokenService.KeyStoreLocation": "<KeyStoreLocation for mTLS communication>"
},
"authTokens": [
{
"type": "Bearer",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC(truncated)YZ_SDLqvwl0cojOsmek_cJAfw_EqdDysfbr8XGXcUKKQpt-uQ",
"http_header": {
"key": "Authorization",
"value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dC(truncated)YZ_SDLqvwl0cojOsmek_cJAfw_EqdDysfbr8XGXcUKKQpt-uQ"
},
"expires_in": "3599"
}
]
}
tokenService.KeyStorePassword
and tokenService.KeyStoreLocation
properties are for the mutual TLS communication against the OAuth server. However, if mTLS is not required or not enabled, you may remove these properties from the definition above.client_assertion
method to request a bearer access token described here is more complex to implement.{
"owner": {
"SubaccountId": "<SubaccountId>",
"InstanceId": null
},
"destinationConfiguration": {
"Name": "Quovadis-AzureAD-JWT",
"Type": "HTTP",
"URL": "https://graph.microsoft.com/v1.0/$metadata:443",
"Authentication": "OAuth2ClientCredentials",
"ProxyType": "Internet",
"tokenServiceURLType": "Dedicated",
"clientId": "8e4ed817-c1b0-4cab-89a0-3bbfaxxxxxxx",
"Description": "OAuth2ClientCredentials",
"tokenService.body.client_assertion": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsIng1dCI6IkRRTU9kcjlJVFp4aGQ2TllJN2w4eTZMSWRUdz0ifQ.eyJpc3MiO(truncated)WjmL07s_ucHfblfVApYfp1m3eeq-_G35MfUCT_HFL-bVMFV6oq9RWuO4NwtCPT_VA23Qgaf6P8HXBwTcQ9aidKl7hJjIgK93t-9zOyASf0ML44KfQMP67j5jVdIY",
"scope": "openid email offline_access https://graph.microsoft.com/.default",
"tokenServiceURL": "https://login.microsoftonline.com/3e2cfdd6-0dc4-4387-a7cf-xxxxxxxxxx/oauth2/v2.0/token",
"tokenService.body.client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
},
"authTokens": [
{
"type": "Bearer",
"value": "eyJ0eXAiOiJKV1QiLCJub25jZSI6Ik9IOUdRVk(truncated)xM6d1lwgMpg9VlpjfY2BtO1Nxp9rYfmgsl77QFcYnBNfj40zWQEGW8QGCk_EzzNXRTUIA"",
"http_header": {
"key": "Authorization",
"value": "Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6Ik9IOUdRVk(truncated)xM6d1lwgMpg9VlpjfY2BtO1Nxp9rYfmgsl77QFcYnBNfj40zWQEGW8QGCk_EzzNXRTUIA"
},
"expires_in": "3599"
}
]
}
tokenService.body.client_assertion
. More details in the appendix here.{
"typ": "JWT",
"nonce": "<nonce>",
"alg": "RS256",
"x5t": "<x5t>",
"kid": "<kid>"
}.{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/<azureAD_tenant_id>/",
"iat": 1663621969,
"nbf": 1663621969,
"exp": 1663625869,
"aio": "<aio>",
"app_displayname": "QuoVadis-Web",
"appid": "8e4ed817-c1b0-4cab-89a0-3bbf1234567",
"appidacr": "2",
"idp": "https://sts.windows.net/<azureAD_tenant_id>/",
"idtyp": "app",
"oid": "<oid>",
"rh": "<rh>",
"sub": "<sub>",
"tenant_region_scope": "EU",
"tid": "<azureAD_tenant_id>",
"uti": "<uti>",
"ver": "1.0",
"wids": [
"<wids>"
],
"xms_tcdt": <xms_tcdt>
}.[Signature]
curl https://graph.microsoft.com/v1.0/$metadata -H "Authorization: Bearer <access_token>"
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata",
"value": [
{
"name": "invitations",
"kind": "EntitySet",
"url": "invitations"
},
{
"name": "users",
"kind": "EntitySet",
"url": "users"
},
{
"name": "applicationTemplates",
"kind": "EntitySet",
"url": "applicationTemplates"
},
]
}
id_token
is to use the OAuth 2.0 code grant flow described here: Authorize access to Azure Active Directory web applications using the OAuth 2.0 code grant flow | Mi... and here: Microsoft identity platform code samples | Microsoft DocsQuoVadis-Web | Certificates & secrets |
To compute the assertion, you can use one of the many JWT libraries in the language of your choice - MSAL supports this using .WithCertificate()
. The information is carried by the token in its Header, Claims, and Signature.
const jwt = require('jsonwebtoken');
const { randomUUID } = require('crypto');
const key = '-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDFz/eQv30tj5oC\nLjT1Im7OtVAVo6mB/wQbEpbOh3LSI8h/f00fwLMJ/uQ3nYHiwqsElTvKA0h0B5tm\n79w(truncaated)LSxBTGdiZznqgLKnImxU1WDSA2xlKJy7J\nAwx8lLYgANSJ7qkKPgPR/t5ZHrx/plY=\n-----END PRIVATE KEY-----\n';
// https://www.samltool.com/fingerprint.php
// the fingerprint can be fetched from the OIDC application manifest
const fingerprint = '0d030e76bf484d9c6177a35823b97ccba1234567';
// https://stackoverflow.com/a/56236665
//
function hex2bin(hexSource) {
var bin = '';
for (var i=0;i<hexSource.length;i=i+2) {
bin += String.fromCharCode(hexdec(hexSource.substr(i,2)));
}
return bin;
}
function hexdec(hexString) {
hexString = (hexString + '').replace(/[^a-f0-9]/gi, '')
return parseInt(hexString, 16)
}
const x5t = hex2bin(fingerprint);
function generateAccessToken_azure(client_id, token_endpoint) {
console.log('generateAccessToken_azure: ', randomUUID());
return jwt.sign( {iss: client_id, sub: client_id, aud: token_endpoint, jti: randomUUID() }, key, { algorithm: 'RS256', header: {x5t : btoa(x5t) }, expiresIn: '1d' } );
}
jwt.ms |
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
17 | |
15 | |
11 | |
11 | |
9 | |
8 | |
8 | |
7 | |
7 | |
7 |