Technical Articles
@sap/datasphere-cli: Getting Rid of Passcodes Thanks to OAuth Client Support
This blog post is part of a series of blogs I published about @sap/datasphere-cli. Find all blog posts related to this topic in my overview blog post here.
The Node.js-based Command-Line Interface (CLI) for SAP Datasphere, @sap/datasphere-cli hosted on npmjs.com, allows you to interact with your SAP Datasphere tenant from the terminal or command line. With version 2022.20 of the CLI, you can now use OAuth client authentication instead of passcodes, allowing you to issue multiple commands without the need to re-authenticate for up to 720 hours!
Introduction
With previous versions of @sap/datasphere-cli, you had to provide a passcode for every command you executed. This is already cumbersome in a foreground scenario with you sitting in front of your PC and using the CLI manually on the command line. It gets even worse when trying to use the CLI in the background, for example in an automated script or a CI/CD pipeline.
Thanks to the latest developments in the area of @sap/datasphere-cli, you can now create an OAuth Client for Interactive Usage and authenticate using the provided Client ID and Client Secret. After you created an OAuth Client for Interactive Usage, you can log in once and run multiple commands without the need to authenticate again for the next 720 hours.
In this blog, I show you how to create an OAuth Client for Interactive Usage and different ways how you can use it to issue commands using the CLI.
Please note: This update includes a few additional essential changes I’d like to highlight here:
- Short flags of some options changed from lower case to upper case. Option -s, –space changed to -S, –space (capital -S), option -d, –definitions changed to -D, –definitions (capital –D).
- Commands with a hyphen, except for passcode-url, like cache-*, changed to cache *, eg cache-init changed to cache init.
Creating an OAuth Client
Following the official SAP Help page for creating an OAuth Client for Interactive Usage are the steps to take in a non-SAP data center.
Log in to your SAP Datasphere tenant and navigate to the App Integration page. Depending on whether your tenant is running in a Two Tenant Mode or One Tenant Mode setup, you need to take a different navigation route. In a One Tenant Mode system (that is, your SAP Datasphere and SAP Analytics Cloud system share the same URL, which only differs in the path in parts), you can configure a new OAuth Client in your connected SAP Analytics Cloud tenant. From the app switcher select Analytics (1), choose System > Administration (2), then click on App Integration (3), and finally hit Add a New OAuth Client (4).
Picture 1 – Adding a new OAuth Client
In a Two Tenant Mode system (the URL for both services is different), you can stay in the Data Warehouse app and choose System > Administration (2), then click on App Integration (3), and finally hit Add a New OAuth Client (4).
To create a new OAuth Client to use with the CLI, you only need to provide a meaningful name. As a purpose, choose Interactive Usage and hit Add. You can leave the Redirect URI empty. When creating an OAuth Client for Interactive Usage only, you need to log in once with your business user account to retrieve the initial access token and refresh token you hand over to the CLI for executing commands.
Picture 2 – Creating a new OAuth Client
After you created a new OAuth Client, you can see a new entry on the App Integration overview page. You need to remember/copy the Authorization URL and Token URL for later use (1). To copy the Client ID and Client Secret, hit the Edit OAuth Client button (2).
Picture 3 – New OAuth Client on App Integration Overview Page
You can find the Client ID (1) and the Client Secret (2) in the dialog. Hit the Show secret button (2) to reveal the Client Secret.
Picture 4 – OAuth Client Details
Using OAuth Clients with @sap/datasphere-cli
Now that you have the Authorization URL, Token URL, Client ID, and Client Secret, you can use these information to log in with your business user account using the CLI and then execute as many commands as you like up to 720 hours before you need to log in again and provide a new refresh token. You can provide the secrets in all different ways supported by the CLI, see the README. In addition, you can create a JSON file containing the secrets and pass it to the CLI using the -s, –secrets-file option. In the following section, I will demonstrate how to pass along the secrets using options and using a file.
To get to know the names of the different options for passing the secrets to the CLI, use option -h, –help (more options have been removed to improve readability):
$ datasphere login --help
Usage: datasphere login [options]
log in to your account using interactive OAuth authentication
Options:
-c, --client-id <id> client id for interactive oauth session authentication (optional)
-C, --client-secret <secret> client secret for interactive oauth session authentication (optional)
-A, --authorization-url <url> authorization url for interactive oauth session authentication (optional)
-t, --token-url <url> token url for interactive oauth session authentication (optional)
-h, --help display help for command
Code Sample 1 – Overview of Options for Passing Secrets
Passing Secrets as Options
We can use the options shown above to log in with our business user account for the tenant we created the OAuth Client for. Currently, it is not possible to log in simultaneously with different accounts for the same tenant or multiple tenants.
When running the datasphere login command and specifying the options, a browser window opens requiring you to log in with your business account details. After successful login, a success message is shown and you can close the tab or window.
Picture 5 – Log In Passing Secrets as Options
After you logged in, you can display the locally stored secrets including the access token, refresh token, and expiry time, … using the datasphere secrets show command:
$ datasphere secrets show
{
"client_id": "...",
"client_secret": "...",
"authorization_url": ".../oauth/authorize",
"token_url": ".../oauth/token",
"access_token": "....",
"refresh_token": "...",
"expires_in": 3599,
"token_type": "bearer",
"id_token": "...",
"scope": "...",
"jti": "...",
"expires_after": ...
}
Code Sample 2 – Displaying Locally Stored Secrets
Now that you are logged in, you can run as many commands as you like without the need to pass along a passcode or any other secrets again for the next 720 hours! Isn’t this great?! 😊 The CLI uses the access token and refresh token it retrieved when running the datasphere login command to authenticate any further command. After 720 hours, you have to log out and log in again to refresh the locally stored refresh token before you can execute additional commands.
If you pass the secrets as separate options to the login command, make sure to URI encode the Client ID and Client secret and put them into double quotes.
Passing Secrets via File
To improve security and not reveal any secrets to someone browsing your terminal’s history, you can also put the secrets into a file and point the CLI to the file’s location when logging in or executing another command (to omit the login step, you can retrieve the access token yourself and pass it to the CLI directly to authenticate commands, see next section).
The file must contain either all information needed to retrieve the access token and refresh token like Client ID, Client Secret, Authorization URL, and Token URL, or you can only provide the access and refresh token instead. Whatever values you provide, you have to provide them in the form of a simple JSON file, with the property names being equal to the respective option names with leading — being replaced with empty space, – being replaced by _, so for example –client-id becomes client_id. You can also run the datasphere secrets show command as shown in the above code sample to get an idea about the property names and which properties you can supply.
// my-secrets.json
{
"client_id": "...",
"client_secret": "...",
"authorization_url": ".../oauth/authorize",
"token_url": ".../oauth/token"
}
Code Sample 3 – Secrets JSON File
Then, run the datasphere login command and specify the path to the secrets file to log in:
$ datasphere login --secrets-file /path/to/my-secrets.json
Code Sample 4 – Logging In Using Secrets File
To remove any locally stored secrets, you can run the datasphere logout command. To verify that the secrets have been removed successfully, you can run the datasphere secrets show command and expect it to fail:
$ datasphere logout
$ datasphere secrets show
No secrets exist
Failed to display locally stored secrets for interactive OAuth authentication
Code Sample 5 – Logging Out
Directly Passing Access Tokens and Refresh Tokens
It might make sense in your scenario to directly pass the access token and refresh token you retrieved in another way than using the datasphere login command and pass both tokens to the CLI instead of Client ID, Client Secret, … This way, the CLI uses the passed tokens to authenticate any command. Again, you can pass the tokens as options (use the -h, –help command to get to know the right option names) or use a JSON file as shown before.
$ datasphere cache init --host https://... --access-token a%bak... --refresh-token ce$z2....
Code Sample 6 – Passing Access Token and Refresh Token as Options
Retrieving Access and Refresh Tokens Manually
Are you using Postman? If yes, check out this blog blogs.sap.com/how-to-setup-postman-for-consuming-sap-datasphere-apis/ which describes how to configure Postman to retrieve the access token using the interactive OAuth client.
If you are using the CLI in an automated setup, for example, a script or a CI/CD pipeline, you may want to retrieve the access token and refresh token manually, and provide the tokens together with the information about the Authorization URL, Token URL, Client ID, and Client Secret in a file to the CLI. This way, you don’t need to log in first before executing commands, and you can update the refresh token shortly before it expires after 720 hours.
There are at least two different ways how to retrieve the tokens manually. Either you can use the datasphere login command and then run datasphere secrets show to display the tokens, or you can follow the login steps manually and use an HTTP client tool of your choice, for example, Postman or curl, to retrieve the tokens. Above you have already read about how to work with the two commands datasphere login and datasphere secrets show. In this section, I will show you how to follow the login steps manually and retrieve the tokens using Postman and curl.
First, you need to retrieve the temporary code which you later use to retrieve the actual tokens. To retrieve the code, you use the Authorization URL you retrieved from the App Integration page in your tenant. You construct the target URL following this syntax:
<authorization URL>?response_type=code&client_id=<client ID>
https://myhost.com/oauth/authorize??response_type=code&client_id=myclientid
Code Sample 7 – URL to Retrieve Temporary Code
Make sure to URI encode the Client ID. Open this URL in your browser. If not logged in already, you are forwarded to your IdP’s login page.
Picture 6 – Login Page
After you logged in, you are forwarded to the Redirect URI you entered when creating the OAuth Client, or localhost:8080/?code=<code> if you did not enter any explicit Redirect URI. In any way, the temporary code is added as a query parameter to the URL.
Picture 7 – Temporary Code as Query Parameter
Second, you need to send a POST request to the Token URL including the temporary code, Client ID, and Client Secret. The response contains the tokens, besides other things.
Picture 8 – Basic Auth with Client ID and Client Secret
Picture 9 – Custom Header x-sap-sac-custom-auth
Picture 10 – Temporary Code in Request Body
When the response returns with status 200 OK, you can find the tokens in the response body.
Picture 11 – Tokens in Response Body
Alternatively, you can use curl. To base64-encode the Client ID and Client Secret, you can, for example, use an online encoder (ideally you do this offline though, to ensure no secret information is shared with anyone).
$ curl -X POST <token URL> \
-H "Authorization: Basic <Base64 encoded <client ID>:<client Secret>>" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "x-sap-sac-custom-auth: true" \
-d "response_type=token&grant_type=authorization_code&code=<code>"
Code Sample 8 – Use curl to Retrieve Tokens
You can paste the tokens into a JSON file and pass it to the CLI using the -s, –secrets-file option or pass the tokens directly to the CLI, for example as options as seen in Code Sample 6 above.
Conclusion
I’d be happy to hear your thoughts, ideas, and comments on this tool and what you think would be a nice-to-have enhancement to the CLI, making your life and work with SAP Datasphere easier. Let me know in the comments!
Further Reading
Command-Line Interface for SAP Datasphere Cloud on npmjs.com
Hi Jascha,
thank you for the update! I really use the cli a lot and it makes my daily work a lot easier. I will try the OAuth possibility as soon as I find time and then update my scripts based on the cli accordingly.
Best wishes,
Jens
Great!
looking forward to having a moment to try it out!
Hello Jascha Kanngiesser
Thanks for the nice Introduction of the new Functionality.
I have added your Information to my Blog - proper SAC Connection
Best Regards Roland
Hi Jascha Kanngiesser
thanks for explaining this update. The new login works fine if I do it from the command line. However, when I use it As a Node.js module dependency as described here, it fails to initialize the cache:
Do you have any idea why this fails? Is it something I am doing wrong?
Best Regards,
Malte
Hi Malte Haring
you're providing the client ID, client secret, authorization URL, and token URL. To log in, the CLI still requires a temporary code, which it retrieves from the browser. When you run the command manually in the terminal, you'll notice that a browser window opens, you need to log in using your business user credentials, then in the address bar a code is visible. The CLI then requests the access and refresh tokens for your user from the server, using the code (business user-specific), client ID, client secret, and authorization URL. Opening the browser probably doesn't work in your script, thus the script fails.
Now, if you want to log in programmatically, you need to find a way to either (1) provide the code (through option --code), because the CLI will then request the tokens, or (2) you request the tokens manually and then provide the tokens to the CLI (--access-token, --refresh-token), no need to do an explicit log in then in your script. In my blog, see above, I describe how to retrieve the tokens manually through various ways.
The second (2) way would be the preferred way if you build a script to use the CLI. You can retrieve the tokens by running the dwc login command manually, then run dwc secrets show, copy the secrets, and use them in your code. You probably want to put them into a secrets file (--secrets-file) or provide them through environment variables (ACCESS_TOKEN, REFRESH_TOKEN), to not put them into your code explicitly. This way, your script will function for up to 720hours/30 days without the need to update the tokens. Afterward, you need to manually log in again, and provide the new tokens to your script.
Please reply back in case something's not clear. 🙂
Please note that there's a bug in versions 2022.20.0 and 2022.21.0 which prevents the CLI from refreshing the access token you provide through the respective options, secrets file, or environment variables. After one hour your script will stop working. This bug is fixed with 2022.22.0 (to be released in two weeks).
Thanks,
Jascha
Hi Jascha Kanngiesser,
thanks for you help. I now retrieved the access token and refresh token manually and added them to my script for testing. This solves the previous error "all handlers failed". However, I now receive another error that I am struggling with:
I have a feeling that the string of the access-token cannot be handled for some reason. Any ideas?
Best Regards,
Malte
Hi Malte Haring
is that the full log/stack trace you get?
I could assume that it fails because it cannot write the discovery file it requests from the server to the file system.
Can you set environment variables? If yes, you can set the environment variable LOG_LEVEL=6, run the command again, and share the full output.
Thanks,
Jascha
Hello @Jascha Kanngiesser,
thanks for your input. Just to be clear. The script works fine when I execute it straight in node.js command line on my client. However, currently I am testing my script with RunKit because in the end, I would like to call the @sap/dwc-cli from C# (by using another library). Testing the script in RunKit ends with the previously described error. Setting the environment variable does not help here.
Do you have any other ideas, why the external call of @sap/dwc-cli does not work?
Best Regards,
Malte
GitHub - JeringTech/Javascript.NodeJS: Invoke Javascript in NodeJS, from C#
Hi Malte Haring
unfortunately, I haven't worked with RunKit yet. There are only three things I can image, taking into account your statement that it works on your client. Either RunKit doesn't allow external HTTP calls from an installed dependency, or you are not allowed to write to the RunKit-local file system to store the response of the cache init call, or there's an issue with the access token handling. Can you please try again with 2022.22, released probably this week (check back tomorrow or on Wednesday) and try again? Version 2022.22.0 contains various fixes related to access token handling.
Thanks,
Jascha
Hi Jascha Kanngiesser ,
I tried it with Version 2022.22.0. Unfortunately, it does still not work for external calls of @sap/dwc-cli.(from C#).
I assume it has nothing to do with the access token handling.
It is more likely that it is not working due to issues with the directory. Is it possible to somehow retrieve the response of cache init call without writing a file into a directory?
Best Regards,
Malte
Hi Malte Haring
I played around with runKit a little more, but could not find any root cause for this issue. :/ As of today, unfortunately, the cache cannot be kept in memory, the CLI doesn't support it yet.
Thanks,
Jascha
Hi Jascha Kanngiesser,
thank you so much for your efforts! Since I only want to read space definitions I am now trying a different approach. I want to use the API that is used by CLI directly:
HTTP request
For the authorization I followed the steps described in the DWC documentation:
https://help.sap.com/docs/SAP_DATA_WAREHOUSE_CLOUD/9f804b8efa8043539289f42f372c4862/74dc6275b23543d2b6aeba94eb538c64.html
To read the space definitions I perform a:
GET https://<DWC URL>/dwaas-core/api/v1/content?space=<space name>&spaceDefinition=true&definitions=true&connections=true
For some reason it says that I am not authorized. However, when I use the token after logging in with CLI, this approach seems to work.
I assume some additional steps are done by the CLI.
Do you have any suggestions? Not sure if this is still your subject.
Best Regards,
Malte
Hi Malte Haring
interesting that it doesn't work when you request the token manually using Postman. The CLI does the very same, as you can see in the code of the module. But as long as the token the CLI receives works for you, you're good to go with this one, too. 😉
Thanks,
Jascha
Hi Jascha Kanngiesser,
the issue is that I want to read the space data programmatically and as long as I have to fetch the token via CLI, I am back at my previous cache issue. Are you sure that CLI gets the token via the procedure described here:
https://help.sap.com/docs/SAP_DATA_WAREHOUSE_CLOUD/9f804b8efa8043539289f42f372c4862/74dc6275b23543d2b6aeba94eb538c64.html
I have a feeling that the user still needs somehow to log into DWC with the actual user.
Best Regards,
Malte
Hi Malte Haring
that's true, as you require a business-user specific access token. However, once you logged in once, you can use the refresh token for the next 720 hours to refresh the access token without the need to log in again. After the 720 hours you need to log in again once to get a new refresh token.
Thanks,
Jascha
Hi Jascha Kanngiesser ,
the fact that the business-user login is required is impractical for a programmatical approach since there needs to be a user interaction with the browser at some point. Do you know if this will be changed in the future so that the business-user login is not required anymore to access the REST API?
The SAC Tentant API for example does not require a business-user login to retrieve data.
Do you have a contact for us which is responsible for the REST API of DWC?
Best Regards,
Malte
Hi Malte Haring
indeed SAC supports both, 2-legged and 3-legged OAuth authentication flows. Unfortunately, DWC does not yet support a 2-legged OAuth authentication flow. We know this limits the usage of the CLI and APIs of DWC in certain situations, and are looking for a way to enable technical/2-legged OAuth authentication flow for DWC, too. I cannot share any timelines yet when this would be available, though.
Thank you,
Jascha
Hi Jascha Kanngiesser ,
thank you so much! Great News! We are looking forward to it.
Best Regards,
Malte
Is there any update on timelines for this yet? I couldn't find anything on the roadmap relating to it. We have the same issue of using the CLI for scheduling automatically form a third party scheduler (Redwood Cronacle) and obviously having to renew every month is an issue for us.
Happy new year!
Thanks for your blog posts.
I have an issue with the hard coded port 8080 in the file :
Hi Vincent CHEVALIER
thank you for your comment! We will release a version soon with the possibility to configure the port using an environment variable. What out for changes mentioned in the CHANGELOG.md file for upcoming versions!
Thanks,
Jascha
This CLI tool assumes read/write access to node_modules/@sap/dwc-cli/.cache folder where it places secrets.json file. So, if you npm -g install using an admin user, you will need to provide rwx auth for the node_modules/@sap/dwc-cli/.cache/secrets.json file for the non-admin user using this tool. Not sure secrets.json needs to be place in .cache though.. might want to check
Hi KK Ramamoorthy
thank you for your comment! Indeed, that's not ideal. In upcoming versions of the CLI we will change the behavior to create the .cache folder and all files in it in the user's home directory.
Thank you,
Jascha
Hello Jascha, When I try to connect to data sphere via CLI , I see following error :
"Failed to log in to your account using interactive OAuth authentication"
my versions :
npm: '9.5.1',
node: '18.16.1'
datasphere version : 2023.14.0
did I miss anything? as per role I have administrator access.
regards
sagar
Hi SAGAR SUDALAKUNTA VORSA
can you add --verbose option when running datasphere login and share the output or send it to jascha.kanngiesser@sap.com?
Thanks,
Jascha
Yes, will do
regards
sagar
Hi SAGAR SUDALAKUNTA VORSA
What was the problem here? I am having the same issue...
Kind regards,
Cas
Hello Cas Criel
My issue is documented here after discussion with jascha :
https://blogs.sap.com/2023/06/30/faq-troubleshooting-guide-for-sap-datasphere-cli/
thanks
sagar