Authentication to SAS Viya: a couple of approaches

33

Need to authenticate on REST API calls

In my blog series regarding SAS REST APIs (you can read all of my posts on this topic here) I outlined how to integrate SAS analytical capabilities into applications. I detailed how to construct REST calls, build body parameters and interpret the responses. I've not yet covered authentication for the operations, purposefully putting the cart before the horse. If you're not authenticated, you can't do much, so this post will help to get the horse and cart in the right order.

While researching authentication I ran into multiple, informative articles and papers on SAS and OAuth. A non-exhaustive list includes Stuart Rogers' article on SAS Viya authentication options, one of which is OAuth. Also, I found several resources on connecting to external applications from SAS with explanations of OAuth. For example, Joseph Henry provides an overview of OAuth and using it with PROC HTTP and Chris Hemedinger explains securing REST API credentials in SAS programs in this article. Finally, the SAS Viya REST API documentation covers details on application registration and access token generation.

Consider this post a quick guide to summarize these resources and shed light on authenticating via authorization code and passwords.

What OAuth grant type should I use?

Choosing the grant method to get an access token with OAuth depends entirely on your application. You can get more information on which grant type to choose here. This post covers two grant methods: authorization code and password. Authorization code grants are generally used with web applications and considered the safest choice. Password grants are most often used by mobile apps and applied in more trusted environments.

The process, briefly

Getting an external application connected to the SAS Viya platform requires the following steps:

  1. Use the SAS Viya configuration server's Consul token to obtain an Access Token to register a new Client ID
  2. Use the Access Token to register the new client ID and secret
  3. Obtain the authorization code
  4. Acquire the OAuth access token of the Client ID using the authorization code
  5. Call the SAS Viya API using the access token for the authentication.

This process requires multiple roles. A SAS administrator performs steps 1 and 2. A script or app provides the URL to an end user for step 3. The end user comes back with the authorization code and plugs it back into the script or application, so it can continue.

Registering the client and getting the authorization code (steps 1,2, and 3) are one-time processes. The access and refresh tokens (step 4) are created once and only need to be refreshed if/when the token expires. Once you have the access token, you can call any API (step 5) if your access token is valid.

Get an access token using an authorization code

Step 1: Get the SAS Viya Consul token to register a new client

The first step to register the client is to get the consul token from the SAS server. As a SAS administrator (sudo user), access the consul token using the following command:

$ export CONSUL_TOKEN=`cat /opt/sas/viya/config/etc/SASSecurityCertificateFramework/tokens/consul/default/client.token`
64e01b03-7dab-41be-a104-2231f99d7dd8

The Consul token returns and is used to obtain an access token used to register the new application. Use the following cURL command to obtain the token:

$ curl -k -X POST "https://sasserver.demo.sas.com/SASLogon/oauth/clients/consul?callback=false&serviceId=app" \
     -H "X-Consul-Token: $CONSUL_TOKEN"
 {"access_token":"eyJhbGciOiJSUzI1NiIsIm...","token_type":"bearer","expires_in":35999,"scope":"uaa.admin","jti":"de81c7f3cca645ac807f18dc0d186331"}

The returned token can be lengthy. To assist in later use, create an environment variable from the returned token:

$ export IDTOKEN="eyJhbGciOiJSUzI1NiIsIm..."

Step 2: Register the new client

Change the client_id, client_secret, and scopes in the code below. Scopes should always include "openid" along with any other groups this client needs to get in the access tokens. You can specify "*" but then the user gets prompted for all their groups, which is tedious. The example below just uses one group named "group1".

$ curl -k -X POST "https://sasserver.demo.sas.com/SASLogon/oauth/clients" \
       -H "Content-Type: application/json" \
       -H "Authorization: Bearer $IDTOKEN" \
       -d '{
        "client_id": "myclientid", 
        "client_secret": "myclientsecret",
        "scope": ["openid", "group1"],
        "authorized_grant_types": ["authorization_code","refresh_token"],
        "redirect_uri": "urn:ietf:wg:oauth:2.0:oob"
       }'
{"scope":["openid","group1"],"client_id":"app","resource_ids":["none"],"authorized_grant_types":["refresh_token","authorization_code"],"redirect_uri":["urn:ietf:wg:oauth:2.0:oob"],"autoapprove":[],"authorities":["uaa.none"],"lastModified":1547138692523,"required_user_groups":[]}

Step 3: Approve access to get authentication code

Place the following URL in a browser. Change the hostname and myclientid in the URL as needed.

https://sasserver.demo.sas.com/SASLogon/oauth/authorize?client_id=myclientid&response_type=code

The browser redirects to the SAS login screen. Log in with your SAS user credentials.

SAS Login Screen

On the Authorize Access screen, select the openid checkbox (and any other required groups) and click the Authorize Access button.

Authorize Access form

After submitting the form, you'll see an authorization code. For example, "lB1sxkaCfg". You will use this code in the next step.

Authorization Code displays

Step 4: Get an access token using the authorization code

Now we have the authorization code and we'll use it in the following cURL command to get the access token to SAS.

$ curl -k https://sasserver.demo.sas.com/SASLogon/oauth/token -H "Accept: application/json" -H "Content-Type: application/x-www-form-urlencoded" \
     -u "myclientid:myclientsecret" -d "grant_type=authorization_code&code=YZuKQUg10Z"
{"access_token":"eyJhbGciOiJSUzI1NiIsImtpZ...","token_type":"bearer","refresh_token":"eyJhbGciOiJSUzI1NiIsImtpZC...","expires_in":35999,"scope":"openid","jti":"b35f26197fa849b6a1856eea1c722933"}

We use the returned token to authenticate and authorize the calls made between the client and SAS. We also get a refresh token we use to issue a new token when the current one expires. This way we can avoid repeating all the previous steps. I explain the refresh process further down.

We will again create environment variables for the tokens.

$ export ACCESS_TOKEN="eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ..."
$ export REFRESH_TOKEN="eyJhbGciOiJSUzI1NiIsImtpZC..."

Step 5: Use the access token to call SAS Viya APIs

The prep work is complete. We can now send requests to SAS Viya and get some work done. Below is an example REST call that returns the top level folders.

$ curl -k https://sasserver.demo.sas.com/folders/folders?filter=isNull(parent) -H "Authorization: Bearer $ACCESS_TOKEN"
{"version":1,"links":[{"method":"GET","rel":"preferences","href":"/preferences/preferences/stpweb1","uri":"/preferences/preferences/stpweb1","type":"application/vnd.sas.collection","itemType":"application/vnd.sas.preference"},{"method":"PUT","rel":"createPreferences","href":"/preferences/preferences/stpweb1","uri":"/preferences/preferences/stpweb1","type":"application/vnd.sas.preference","responseType":"application/vnd.sas.collection","responseItemType":"application/vnd.sas.preference"},{"method":"POST","rel":"newPreferences","href":"/preferences/preferences/stpweb1","uri":"/preferences/preferences/stpweb1","type":"application/vnd.sas.collection","responseType":"application/vnd.sas.collection","itemType":"application/vnd.sas.preference","responseItemType":"application/vnd.sas.preference"},{"method":"DELETE","rel":"deletePreferences","href":"/preferences/preferences/stpweb1","uri":"/preferences/preferences/stpweb1","type":"application/vnd.sas.collection","itemType":"application/vnd.sas.preference"},{"method":"PUT","rel":"createPreference","href":"/preferences/preferences/stpweb1/{preferenceId}","uri":"/preferences/preferences/stpweb1/{preferenceId}","type":"application/vnd.sas.preference"}]}

Use the refresh token to get a new access token

To use the refresh token to get a new access token, simply send a cURL command like the following:

$ curl -k https://sasserver.demo.sas.com/SASLogon/oauth/token -H "Accept: application/json" \
     -H "Content-Type: application/x-www-form-urlencoded" -u "myclientid:myclientsecret" -d "grant_type=refresh_token&refresh_token=$REFRESH_TOKEN"
{"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImxlZ...","token_type":"bearer","refresh_token":"eyJhbGciOiJSUzI1NiIsImtpZCSjYxrrNRCF7h0oLhd0Y","expires_in":35999,"scope":"openid","jti":"a5c4456b5beb4493918c389cd5186f02"}

Note the access token is new, and the refresh token remains static. Use the new token for future REST calls. Make sure to replace the ACCESS_TOKEN variable with the new token. Also, the access token has a default life of ten hours before it expires. Most applications deal with expiring and refreshing tokens programmatically. If you wish to change the default expiry of an access token in SAS, make a configuration change in the JWT properties in SAS.

Get an access token using a password

The steps to obtain an access token with a password are the same as with the authorization code. I highlight the differences below, without repeating all the steps.
The process for accessing the ID Token and using it to get an access token for registering the client is the same as described earlier. The first difference when using password authentication is when registering the client. In the code below, notice the key authorized_grant_types has a value of password, not authorization code.

$ curl -k -X POST https://sasserver.demo.sas.com/SASLogon/oauth/clients -H "Content-Type: application/json" \
       -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZ..." \
       -d '{
        "client_id": "myclientid", 
        "client_secret": "myclientsecret",
        "scope": ["openid", "group1"],
        "authorized_grant_types": ["password","refresh_token"],
        "redirect_uri": "urn:ietf:wg:oauth:2.0:oob"
        }'
{"scope":["openid","group1"],"client_id":"myclientid","resource_ids":["none"],"authorized_grant_types":["refresh_token","authorization_code"],"redirect_uri":["urn:ietf:wg:oauth:2.0:oob"],"autoapprove":[],"authorities":["uaa.none"],"lastModified":1547801596527,"required_user_groups":[]}

The client is now registered on the SAS Viya server. To get the access token, we send a command like we did when using the authorization code, just using the username and password.

curl -k https://sasserver.demo.sas.com/SASLogon/oauth/token \
     -H "Content-Type: application/x-www-form-urlencoded" -u "myclientid:myclientsecret" -d "grant_type=password&username=sasdemo&password=mypassword"
{"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6Imx...","token_type":"bearer","refresh_token":"eyJhbGciOiJSUzI1NiIsImtpZ...","expires_in":43199,"scope":"DataBuilders ApplicationAdministrators SASScoreUsers clients.read clients.secret uaa.resource openid PlanningAdministrators uaa.admin clients.admin EsriUsers scim.read SASAdministrators PlanningUsers clients.write scim.write","jti":"073bdcbc6dc94384bcf9b47dc8b7e479"}

From here, sending requests and refreshing the token steps are identical to the method explained in the authorization code example.

Final thoughts

At first, OAuth seems a little intimidating; however, after registering the client and creating the access and refresh tokens, the application will handle all authentication components . This process runs smoothly if you plan and make decisions up front. I hope this guide clears up any question you may have on securing your application with SAS. Please leave questions or comments below.

Share

About Author

Joe Furbee

Developer Advocate

Joe, a Developer Advocate at SAS, acts as a liaison for the developer community to SAS technologies. His enjoys chronicling his journey as he learns SAS and open source integration. Joe comes to SAS with a software development and testing background.

33 Comments

  1. Step 4 "app:appclientsecret" should be "myclientid:myclientsecret" if you want to be consistent with Step 2.

  2. How to delete a client_id? Tried to send method DELETE to /SASLogon/oauth/clients but it says it only accept GET and POST.

  3. Hello,

    I assumed the REFRESH_TOKEN would not expire, but it did. I tried to request another ACCESS_TOKEN (continuing with a test I started a couple of weeks ago) but I got an error message returned: {"error":"invalid_token","error_description":"Invalid refresh token (expired): eyJhbGciOiJSUz...Hnses0K5UMa8 expired at Fri Apr 05 15:55:25 CEST 2019"}

    If the REFRESH_TOKEN expires, what is the action to take?

    Is it possible to not have an expiration date for the REFRESH_TOKEN ?

    Thank you!

    • Joe Furbee
      Joe Furbee on

      Hi C. Below are answers to your questions:
      I assumed the REFRESH_TOKEN would not expire, but it did. -- REFRESH_TOKENS expire 30 days after being issued by SAS Viya; this is a configurable value on SAS Viya; see below for more details.

      Note: If you use the REFRESH_TOKEN to get a new ACCESS_TOKEN, a new REFRESH_TOKEN is generated, but has the same ttl as the original REFRESH_TOKEN, not 30 additional days.

      If the REFRESH_TOKEN expires, what is the action to take? -- You will need to go to the user again to get a new REFRESH_TOKEN. Request a new ACCESS_TOKEN using the authorization code grant type.

      Is it possible to not have an expiration date for the REFRESH_TOKEN ? -- The REFRESH_TOKEN lifetime and ACCESS_TOKEN lifetime can be extended out as far as you want (1 year, 20 years, etc.) See the sas.logon.jwt configuration in SAS EV.

  4. I do not see any clients, which were added by following the instructions in this article, when calling /SASLogon/oauth/clients via GET. I only see the standard SAS Viya ones like SAS Drive,..

    (I am able to add a client, authenticate via refresh/access token, and successfully call the SAS Viya API)

    Have you seen any custom clients by calling /SASLogon/oauth/clients via GET?

    • Joe Furbee
      Joe Furbee on

      Hi C. Thanks for reading and posting your question. I went into my env and registered a dummy client - myclient using this command:
      curl -k -X POST "https://sasserver.demo.sas.com/SASLogon/oauth/clients" -H "Content-Type: application/json" -H "Authorization: Bearer $IDTOKEN" -d '{"client_id": "myclientid", "client_secret": "myclientsecret","scope": ["openid", "group1"],"authorized_grant_types": ["authorization_code","refresh_token"],"redirect_uri": "urn:ietf:wg:oauth:2.0:oob"}'

      I then went to Postman and ran the following GET: http://sasserver.demo.sas.com/SASLogon/oauth/clients. I received back all registered clients, including myclientid.

      Further, I filtered on myclientid with this GET: http://sasserver.demo.sas.com/SASLogon/oauth/clients/myclientid. I received the following response:
      {"scope": ["openid","group1"],"client_id": "myclientid","resource_ids": ["none"],"authorized_grant_types": ["refresh_token","authorization_code"],"redirect_uri": ["urn:ietf:wg:oauth:2.0:oob"],"autoapprove": [],"authorities": ["uaa.none"],"lastModified": 1557321328647,"required_user_groups": []}

      Are these the steps you followed? Does this point you in the right direction?

  5. Hi Joe,

    a question about this element of the token & OAuth process: "scope": ["openid", "group1"]

    Does the "group1" item refer to "Custom Groups" within Viya or to something else? Presumably this can be used to restrict access if this is the case?

    thenaks

    Alan

    • Joe Furbee

      Hi Alan,
      Scopes should always include "openid" along with any other groups that this client needs to get in the access tokens. You can specify "*" but then the user will get prompted for all their groups, which is tedious. The example in the article uses one group named "group1", but in production would be customized to fit customer needs.

      • I'm not sure I followed the answer to the question. In your example, is group1 a Custom Group within Viya, or an LDAP group? Or can it be either?
        Likewise, if I'm using this client app/secret to get a token just for autdomain support, can/should I limit the applicability of the token via the resource_ids values? Woudl I be specifiing the id of the authdomain in that scenario?

  6. Hi Joe,

    I am a novice developer trying to implement and learn something.

    For step 2: "client_id": "myclientid",
    "client_secret": "myclientsecret"

    I do not have an API or client that I can use here. What are the default values or can I make up anything? and use it later for authentication.

    I tried to use client_id: report and a generic secret, and I was able to run this code

    curl -X POST "https://sasvad01.wcuds.net/SASLogon/oauth/clients" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $IDTOKEN" \
    -d '{
    "client_id": "report",
    "client_secret": "test",
    "scope": ["openid"],
    "authorized_grant_types": ["password"],
    "access_token_validity": 43199
    }'

    but then I am stuck in step 3. When I try to access the url with the client name it says : The request is invalid.
    A redirect_uri can only be used by implicit or authorization_code grant types.

    Can you please let me know how to delete the clients?

    I also tried this:

    curl -k -X POST "https://sasvad01.wcuds.net/SASLogon/oauth/clients" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $IDTOKEN" \
    -d '{
    "client_id": "report2",
    "client_secret": "test",
    "scope": ["openid"],
    "authorized_grant_types": ["authorization_code","refresh_token"],
    "redirect_uri": "urn:ietf:wg:oauth:2.0:oob"
    }'
    {"scope":["openid"],"client_id":"report","resource_ids":["none"],"authorized_grant_types":["refresh_token","authorization_code"],"redirect_uri":["urn:ietf:wg:oauth:2.0:oob"],"autoapprove":[],"authorities":["uaa.none"],"lastModified":1547138692523,"required_user_groups":[]}

    but it said
    -bash: scope:[openid]: command not found

    • Joe Furbee

      Hi Ravi,
      First, to delete clients, please see the previous comment from Edo on April 21, 2019 6:10 pm. The answer to your question can be found there.

      As for the clientid question, this is the trickiest part of getting started. You'll need to register the client first and to do this you'll need admin privileges or have someone with admin complete the task outlined in this documentation. Here is another set of instructions for completing the same task from developer.sas.com. To summarize what to do:

      1. Locate a valid Consul token - usually found here: /opt/sas/viya/config/etc/SASSecurityCertificateFramework/tokens/consul/default/client.token
      2. Request an OAuth token to use on the registration call - The sample command below is for registering a client named "report"
      curl -X POST "http://example.com/SASLogon/oauth/clients/consul?callback=false&serviceId=report" -H "X-Consul-Token: \"
      3. The command above will return an access_token. Use the token in the command below to register the client (you can leave the client_secret: value blank).
      curl -X POST "https://localhost/SASLogon/oauth/clients" -H "Content-Type: application/json" -H "Authorization: Bearer <access -token-goes-here>" -d '{"client_id": "report", client_secret": <secret -goes-here>", "scope": ["openid"], "authorized_grant_types": ["password"], "access_token_validity": 43199}'</secret></access>
      4. If the request is successful, the client is registered.
      5. You can now use "report" as the client_id in the call you posted.

      Hope this helps!

  7. Richard Paterson on

    Hi, first of all thanks for this excellent guide. I have my cURL statement working perfectly. I would however like to run this command via PROC HTTP and this is where things come unstuck.

    curl -k https://sasserver.demo.sas.com/SASLogon/oauth/token \
    -H "Content-Type: application/x-www-form-urlencoded" -u "sas.cli:" -d "grant_type=password&username=sasdemo&password=mypassword"

    I am having difficulty understanding the -u switch in the cURL statement. What is "sas.cli:"? and where do I specify this in the PROC HTTP code ? So far I have this,

    proc http url="http://sasviya01.race.sas.com/SASLogon/oauth/token" method="GET" auth_basic
    in="grant_type=password"
    webauthdomain="sas.cli:"
    webusername="geladm"
    webpassword="lnxsas"
    out=response
    headerout=hdrout
    headerout_overwrite;
    headers "Accept"="application/json";
    run;

    but this is giving me an access error.

    any help would be very much appreciated!

    regards,

    Richard

    • Joe Furbee

      Hi Lei,
      Thanks for reading the post and your question. Are you referencing authorization or authentication? Providing a bit more context around your question will assist us in providing a complete answer. Thanks!

  8. jianlin huang on

    Hi, Does a user password change affect the generated access_token (it will expire, or no effect?) . Thanks.

    • Joe Furbee

      Hi Jianlin,
      Great question! The answer is 'No', a password change will not revoke the access token generated using grant_type=password. SAS Viya does not store the password when creating the access token, so it would not know when a user's password changes.

      Thanks,
      Joe

      • jianlin huang on

        Hi Joe,
        Thanks for your help, I have a project here where a client needs a Token when calling the VIYA REST API, and I'm thinking if I can just give a long-term valid Token. So that what happens when Token becomes unavailable, like a system reboot, migration? In addition, do you know where the Token information exists and what commands are available to view all them in the system? Thanks again.

        • Joe Furbee
          Joe Furbee on

          Hi Jianlin. In some instances you may have to regenerate the token. You can follow the same steps listed in the article to get the original token. You can also consider using a refresh token. Below I've listed questions/answers from an earlier comment that may apply here:

          I assumed the REFRESH_TOKEN would not expire, but it did. -- REFRESH_TOKENS expire 30 days after being issued by SAS Viya; this is a configurable value on SAS Viya; see below for more details.

          Note: If you use the REFRESH_TOKEN to get a new ACCESS_TOKEN, a new REFRESH_TOKEN is generated, but has the same ttl as the original REFRESH_TOKEN, not 30 additional days.

          If the REFRESH_TOKEN expires, what is the action to take? -- You will need to go to the user again to get a new REFRESH_TOKEN. Request a new ACCESS_TOKEN using the authorization code grant type.

          Is it possible to not have an expiration date for the REFRESH_TOKEN ? -- The REFRESH_TOKEN lifetime and ACCESS_TOKEN lifetime can be extended out as far as you want (1 year, 20 years, etc.) See the sas.logon.jwt configuration in SAS EV.

          I hope this helps and if you have further questions, please let me know.

          • jianlin huang on

            Thank you very much, if I set ACCESS_TOKEN to not expire (e.g10 years), and don't need to use REFRESH_TOKEN, is that okay? Of course there may be some safety issues.

  9. Hi Joe

    In you example of generating access token using password, you have used 'clientid' and 'myclientsecret' in the client id and secret field. but while authorizing the access you have used -u "sas.cli:".
    what is this sas.cli client id, could you please give us some more insights?

    Thanks
    sounak

    • Joe Furbee
      Joe Furbee on

      Hi Sounak. Thanks for reading and your question. This is an oversight in my cURL comamnd. The -u value should be, clientid:clientsecret, which to keep consistent with my other commands throughout the post should be -u "myclientid:myclientsecret". I have made this adjustment in two place in the article. It now appears properly. Please let me know if you have further questions.

  10. Hi Joe,

    Thanks for this post. My question is related to CAS APIs. With the access tokens generated using above methods, I was able to load a csv in caslib public. However, with the same token if I try to create a global caslib it fails with "ERROR: You do not have permission to create global caslibs." With the credentials I supplied while creating the access tokens if I login to Environment Manager, I can create a global caslib. I can even use the credentials in curl with -u user:password and can successfully create global caslib.

    Can you tell what is preventing from creating a global caslib using access tokens generated by the steps you outline above? I verified that the identity I used to generate access tokens is present in 'caslib management privileges'. In short, do you know how to add the registered client (from step 2 above) in 'caslib management privileges'?

    Thanks,
    Pravin

    • Joe Furbee

      Hi Pravin,
      I'm still looking into this, but the first thing that comes to mind is to check/verify the 'scopes' used during client registration. If you have the ability to re-register your client, try with scope: ['openid', '*']. You can read more about authentication on SAS Viya in this SASGF paper: OpenID Connect Opens the Door to SAS® Viya® APIs.

      Also, what client are you using to make the request? What REST endpoint are you using? Can you provide the entire call?

      • curl -s -X POST https://example.abc.com:8777/cas/sessions/58487c47-5ed6-de4d-b77b-1672b7c27d3d/actions/table.addCaslib -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"name":"pcas","path":"/data/pcas","description":"Pravin caslib","subDirectories":"false","session":"false","dataSource":{"srcType":"path"},"createDirectory":"true","hidden":"false","transient":"false"}'

        results in

        "logEntries": [
        { "message": "NOTE: The specified directory for caslib pcas already exists.", "level": "info" },
        { "message": "ERROR: You do not have permission to create global caslibs.", "level": "error" },
        { "message": "ERROR: The action stopped due to errors.", "level": "error" }
        ],

Leave A Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Back to Top