Technical Articles
Unchain CAP – Authenticate to a CAP service using Azure AD B2C
Why?
Right now the SAP Cloud Application Programming Model (CAP) is very focused to be deployed either on SAP Cloud Platform Cloud Foundry or SAP HANA. The runtimes in Node.JS and Java are very portable. But for authentication only the SAP HANA or SAP Cloud Platform Cloud Foundry UAA Service and for persistency only SQLite (for development) and SAP HANA are supported. With some limitations PostgreSQL is supported in CAP Java.
In this post I want to show that the XSUAA Service is not the only option for authentication. I’ve also started the project cap-postgres that does the first little steps to support PostgreSQL with Node.JS beside SQLite and SAP HANA. I hope that the CAP Team get’s the option to open up more and support such initiatives. I think the broader the supported environments are, the broader the adoption can be.
How?
I’m quite a fan of the Azure Active Directory B2C (Azure AD B2C in short). Together with CONCETO I’ve implemented it for several SAP customers to have a central Identity Provider (IdP) for their customers. We’ve used it in combination with SAP Cloud Platform Neo but also for an SAPUI5 App hosted on SAP NetWeaver Java. Here the now also properly documented SAML2 support using custom policies comes in handy. But let’s get into the meat of the matter: how can we use Azure AD B2C to get an authenticated request to a CAP service implementation?
Having a working application as a starting point sounds like a good idea or? So I followed Quickstart Guide: Set up sign-in for a single-page app using Azure Active Directory B2C. That resulted in a HTML frontend that uses Microsoft Authentication Library for JavaScript (MSAL.js) for authentication. The JSON Web Token (JWT) received using the authentication is then used to call a backend service implemented in JavaScript using the Node.JS runtime. There the JWT is verified using the npm package Microsoft Azure Active Directory Passport.js Plug-In.
With that working example in place I’ve started a fresh CAP project using the command:
cds init cap-azure-ad-b2c --modules db,srv
Following Getting Started in a Nutshell I’ve created a simple data model and service serving only a list of books that I’ve populated with one entry. Using the annotation @(requires: ‘authenticated-user’) I’ve restricted the access to the service as described in User-Specific Restrictions with @requires and @restrict. That restriction can be tested locally by following the CAP Documentation Mocked Authentication.
My first working iteration was quite a hack. As the JSON Web Token (JWT) that is created by Azure AD B2C looks like this:
{
"iss": "https://cswb2b.b2clogin.com/fd18cc3e-9960-4e35-9076-dba588896ff3/v2.0/",
"exp": 1586011467,
"nbf": 1586007867,
"aud": "ac6f2c18-a2c6-4b5f-9148-13abaaf8e5aa",
"sub": "a625892d-353a-4f01-a736-b4436847bfe4",
"name": "unknown",
"given_name": "Gregor",
"family_name": "Wolf",
"tid": "fd18cc3e-9960-4e35-9076-dba588896ff3",
"nonce": "e1446223-967e-45a9-9762-34773b55c808",
"scp": "openid",
"azp": "ac6f2c18-a2c6-4b5f-9148-13abaaf8e5aa",
"ver": "1.0",
"iat": 1586007867
}
The validation failed as no “scope” attribute is provided. This is expected by CAP. Here for comparison the JWT issued by the SAP CP Cloud Foundry or SAP HANA XSUAA:
{
"jti": "eef51d429374461d96b6760fdd55b876",
"ext_attr": {
"enhancer": "XSUAA",
"zdn": "asia"
},
"xs.system.attributes": {
"xs.rolecollections": []
},
"given_name": "Gregor",
"xs.user.attributes": {},
"family_name": "Wolf",
"sub": "26adb6b5-1e16-45b7-85f8-f06041a3a699",
"scope": [
"openid",
"uaa.user"
],
"client_id": "sb-HTML5UserAPIforCF!t128",
"cid": "sb-HTML5UserAPIforCF!t128",
"azp": "sb-HTML5UserAPIforCF!t128",
"grant_type": "authorization_code",
"user_id": "26adb6b5-1e16-45b7-85f8-f06041a3a699",
"origin": "httpstfs.csw.comadfsservicestrust",
"user_name": "Gregor.Wolf@gmail.com",
"email": "Gregor.Wolf@gmail.com",
"auth_time": 1586009488,
"rev_sig": "7ef5522f",
"iat": 1586009489,
"exp": 1586052689,
"iss": "http://asia.localhost:8080/uaa/oauth/token",
"zid": "c5f5acac-a05b-485f-94c6-7854723bcd19",
"aud": [
"sb-HTML5UserAPIforCF!t128",
"uaa",
"openid"
]
}
But some days later Volker Buzek approached me and sent me pull request. Now only one file in the CAP standard must be patched and this approach even supports now different authorisations using a custom attribute in Azure AD B2C to store the scopes.
Next Steps
I hope that this can be the start of OpenCAP. Open up for different means of authentication, persistence and other services. Open up also like OpenUI5 to allow the usage under a permissive licence outside of the SAP ecosystem. Let’s see what we can learn from the talks at the 1st online reCAP conference scheduled for 15th May 2020. Especially “Modularized CAP (e.g. other DBs)” by Marc Becker, Adrian Goerler and Andreas Schoesser seems to be exactly what I’m looking for.
Looking forward for your feedback.
Thanks Gregor for sharing how to extend CAP capabilities to various use cases.
As SAP Approuter is entry point in to multi target applications (or micro services), is it possible to use app router with Azure Active Directory?
Thank you and Best Regards,
Venu
Hello Venu,
when you deploy to SAP CP Cloud Foundry or SAP HANA then you can use the Approuter also to authenticate using the Azure Active Directory. The connection to the IdP (which must be SAML2 compatible) is done on the platform. In the example here I don't use the Approuter as the used Microsoft Authentication Library for JavaScript (MSAL.js) is directly sending the JWT to the service endpoint.
Best regards
Gregor
Thank you Gregor. Very helpful...