Building custom apps on top of SAS Viya, Part Four: Examples

18

Welcome back to my series on securely integrating custom applications into your SAS Viya platform. My goal today is to lay out some examples of the concepts I introduced in the previous posts. As a quick recap:

  1. In the first installment of this series I shared my experiences on a customer project, where we helped achieve value-add on top of their SAS Viya platform. We built a custom application for their buyers to use at auto auctions and embedded repeatable analytics from a SAS Viya decisioning workflow. That customization now helps their buyers make data-driven decisions based on both business rules and analytical models trained by historical data from past purchases and sales. The models are re-trained by the workflow as buyers "commit" new purchases.
  2. In part two, I outlined the security frameworks (OAuth 2.0 and OpenID Connect) and main integration points for bringing custom apps and SAS Viya together. I also introduced the main decision points before setting up this integration.
  3. The most recent post detailed the OAuth flow choices for access tokens from SAS Logon used for custom application and SAS Viya integration. I outlined some suggestions on when to use each flow in order to retrieve the appropriate scoping for your app.

I would encourage you to check out the previous parts in this series before reading on so we all start on a level playing field. In this post, I will assume you have, and thus are familiar with the concepts of user- and general-scoping. Before we jump into some examples for both of those scenarios, two things to note:

  • Recall for both user-scoped and general-scoped scenarios, the Password authentication flow is technically valid, but the industry is suggesting phasing out the use of this flow (source one) (source two). Accordingly, I won't include examples of it here.
  • This post assumes that you are familiar with the SAS Administration concepts of Custom Groups, granting access to SAS Content, and creating authorization rules. If those topics are unfamiliar to you, and you will be implementing this custom application registration, please take a look at the resources linked inline.

General-scoped access tokens

This refers to scenarios where we don't want end users to have to log in (directly to SAS Viya, at least). You don't want the tokens returned by SAS Logon to your custom applications generating authorization decisions based on the individual using your application. In these cases, however, you do still want control over the endpoints/resources your app calls and which CRUD operations (which map to HTTP methods) it performs on them. The general steps are:

  • Create a Custom Group for the use of the application.
  • Grant necessary access to that Custom Group for the appropriate endpoints and resources.
  • Register a new, unique client to your SAS Viya environment, using the client_credentials grant type, and specifying the created Custom Group as the value for the authorities property.

Detailed steps

  1. As a member of your SAS Viya environment's SAS Administrators group, create a new Custom Group for your application. The naming convention of App.<your_app> is recommended. Using prefixes in this manner logically organizes Custom Groups used for similar purposes together. Consider including a description like the one pictured below.

    (Click to enlarge)
  2. Determine the necessary resource(s), content, and/or endpoint(s) application users need. Next, create the authorization rules to provide appropriate access using the Custom Group you created in the previous step. I've often found this to be an iterative process which is perfectly fine. You (a SAS Admin) can adjust the authorization rules granted to this Custom Group, and thus your app, at any time.
    _

  3. Review the general documentation provided for registering clients to your SAS Viya environment. The following is an adaptation of those instructions, please take note of the differences and be sure to include them as you see below.

    (Click to enlarge)

    For readability, the required data for the last step above was:

    {
        "client_id": "{{YOUR_APP_NAME}}",
        "client_secret": "{{YOUR_SECRET}}",
        "authorized_grant_types": "client_credentials",
        "authorities": "{{CUSTOM_GROUP_FROM_PREVIOUS_STEPS}}",
        "scope": "openid"
    }

    Where scope should include openid for authentication purposes.

    And where authorities will include your Custom Group linking this client with the authorization rules you created. This effectively grants those same permissions to any downstream API requests made by your application when it requests a token in the manner below (next step).
    _

  4. Once registered, here is an example (using Postman) of how your application requests a token using the Client Credentials flow:
    (Click to enlarge)

    _

  5. And here is an example of how your application would make an API call using that token. In this example, an authorization rule was created for this endpoint with Read (GET) permissions for the Custom Group linked to this client:
    (Click to enlarge)

Some important things to note

  • You'll want to use dynamic variables instead of hard coded values as you see in the Postman calls above. This is just for illustrative purposes.
  • If you make a GET request against the client you just registered for your application, you will not get back the client_secret value you specified upon creation. Note that as we saw in Step 4 above, your client will need this value to request it's tokens so plan accordingly.
  • In addition to any values that could change depending on where your application is deployed (including hostnames, etc.), ensure your client_id and client_secret values are secured properly. If you're using Docker, feed an environment variables file to docker-compose.yml. Make sure to include the variables file in your gitignore file. Moreover, if you're using k8s you can always use secrets. Just have a plan and never hard code these into your application.
  • When making requests to SAS Logon Manager for tokens within the context of your registered application as shown in the first screenshot above, the default timeout for the tokens returned is just shy of 12 hours. You can augment that by adding the access_token_validity property with a numeric value in seconds when you register your client. Any subsequent tokens obtained using your client will use that timeout value.
  • OAuth's Refresh Token flow isn't available in combination with the client_credentials grant type. Keep this in mind when deciding on the value to use for the access_token_validity property. You may want to consider adding logic to your application to request a new token before the current one expires. Parsing out the value of the expires_in property dynamically from the original token request response will be helpful in accomplishing that. Keep in mind you will want enough of a time buffer if your application makes a string of endpoint requests or any long-running requests, so your process does not get interrupted by an expired token.

_


User-scoped access tokens

This refers to scenarios where it does make sense for your application's end users to log in to SAS. All subsequent API calls into SAS Viya resources your application makes should be within the context of what they, as an individual user, is allowed to do/see/access in SAS Viya. The authorization decisions for those requests will be based on the union of anything the logged-in user could do while logged into a native SAS Viya UI (see autoapprove below) and any authorization rules that were explicitly granted to the Custom Group(s) selected while registering this client. A SAS Administrator can place all the users of your application in that Custom Group, ensuring a similar level of functionality for those users, while still obeying any more-granular rules that might apply, such as on specific data or content. The general steps are:

  • Create a Custom Group for the use of the application
  • Grant necessary access to that Custom Group for the appropriate endpoints and resources.
  • Register a new, unique client to your SAS Viya environment, using the authorization_code grant type, specifying the created Custom Group as the value in the scope property, and some additional specific properties shown below
  • Include logic in your application to handle the redirection to SAS Logon Manager when needed and to temporarily store authorization codes and OAuth tokens per session/user.

Detailed steps: prepping and registering the client

  1. Perform steps 1 and 2 from the General-scoped tokens section above.
    _

  2. Review the general documentation provided for registering clients to your SAS Viya environment. The following is an adaptation of those instructions, please take note of the differences and be sure to include them as you see below.

    (Click to enlarge)

    For readability, the required data for the last step above was:

     {
        "client_id": "{{YOUR_APP_NAME}}",
        "client_secret": "{{YOUR_SECRET}}",
        "authorized_grant_types": ["authorization_code", "refresh_token"],
        "scope": ["openid", "{{CUSTOM_GROUP_FROM_PREVIOUS_STEPS"],
        "redirect_uri": ["{{URL_SASLOGON_SHOULD_REDIRECT_BACK_TO}}/callback"],
        "autoapprove": "{{TRUE_OR_FALSE_IN_LOWCASE}}" 
    }

    Where scope should include openid for authentication purposes and, for this authorization flow, must also include the Custom Group you created. This is what links your client to any authorization rules you created, effectively granting those same permissions to any downstream API requests made by your application.

    And where redirect_uri includes the URL SAS Logon Manager sends your users back to after they authenticate. It's formatted in a list above in case you want to include both HTTP and HTTPS protocols for development purposes. This is helpful in avoiding edits to this client down the road. Take note of the inclusion of the /callback postfix in the screenshot above. We'll see more on this later.

    And where autoapprove is either true or false, depending on whether or not you want to present the user with a list of Custom Groups they need to approve or deny before proceeding. My personal opinion is to stick with true, not only because it more-closely mimics the behavior already present with native SAS Viya applications. In addition, the user likely won't know the implications of selecting (or not selecting) certain Custom Groups. This could lead to downstream authorization issues for your application.
    _

Detailed steps: partial example for your app's logic

Your app has several requirements pertaining to authentication and authorization. First, it needs to know when to redirect users to SAS Logon Manager (when they're not already logged in or if their session expired). The app also must handle the authorization code returned to your user. Further, it needs to request an OAuth token for your user and how to use that token to make downstream requests to other APIs. Finally, your app needs to revoke tokens when necessary.

This is not a complete example and exactly how this is implemented depends heavily on the language your application was developed in. I don't want to distract from the topic at hand, so I'm not going to include complete code blocks. The application used for this example is a Javascript React web UI and I'll focus on the main order of operations involved in getting this to work. Therefore, I've intentionally removed some syntax to illustrate this in a more agnostic way.

  1. User navigates to our application's home page. Logic determines that a boolean state variable we're setting called isAuthenticated is false (default), so it redirects to the SAS Logon page using the URL below. Note that all items in {{ }} are substituted with environment variables rather than hardcoded. Recall, in the previous step when we registered the client, I mentioned you might want to include /callback after the URL to your application. When you're developing the routes in your code logic, it helps to differentiate between someone who has navigated directly to your / or /home page versus a user that's been redirected there from SAS Logon Manager. You'll see why in the next step.
    https://{{VIYA_HOSTNAME}}/SASLogon/oauth/authorize/response_type=code&client_id={{CLIENT_ID}}&redirect_uri={{URL_POSTFIXED_WITH_/callback}}
  2. This automagically presents the user with the same SAS Logon screen they see when accessing a native SAS Viya application. After they successfully authenticate, the URL redirects them back to our application (we included the /callback postfix in the previous step, so we see that in the redirected URL below) with an extra URL parameter like this (the code will of course be dynamic):
    https://{{YOUR_APPS_HOSTNAME}}/callback?code=pe3vyay7LJ
  3. So now, we substring off of the value of the user's current URL path and store that code in a variable, say code, temporarily. Next, the conditional logic will pick up the fact the user does have a code but isAuthenticated is false, so we kick off another function, feeding the code as an argument.
    _
  4. This function makes the second call to SAS Logon Manager; this time to exchange the user's code for an OAuth token by making a request to:
    https://{{VIYA_HOSTNAME}}/SASLogon/oauth/token/

    _
    With headers similar to the snippet below, again where anything in {{ }} is dynamically set:

    -H "Accept: application/json"
    -H "Content-Type: application/x-www-form-urlencoded"
    -u "{{CLIENT_ID}}:{{CLIENT_SECRET}}" -d "grant_type=authorization_code&code={{CODE}}"
  5. If the request is successful, we then call another function, which uses the js-cookie package in our case, to set a new piece of information in the user's cookie called access_token. This value gets pulled from the result of the previous request. At the same time, we update our isAuthenticated state variable to true.
    _
  6. Now that the state has changed (isAuthenticated is true), the page gets re-rendered in typical React fashion. If our URL included /callback then we know the user came from SAS Logon, and our application logic redirects the user to the main /home page of our application. Now we have the user's OAuth token stored in a cookie for them.

At this point, I think we've seen enough examples to know how to make downstream requests with the token by adding an Authorization header and setting it's value to Bearer {{TOKEN}}, so I'll leave those pieces out from here. Don't forget each of those requests will be made for your application's individual logged-in users, so their unique identities will need to be able to access the endpoints/resources in SAS Viya through appropriate authorization rules.

On a similar note, and because this is getting really long 🙂 , it feels a little out-of-scope to include details on the refresh token logic. Just know, it follows a very similar pattern as above. If you've included refresh_token in the authorized_grant_types list when registering your client, then subsequent token requests made within the context of your client automatically include a refresh_token property in the response you can parse out and use to get a new bearer token. The logout logic includes removing the user's cookie.

Just one more thing: if you have a multi-machine SAS Viya environment then using {{VIYA_HOSTNAME}} might have been confusing. Check your Ansible inventory.ini file and look for the machine specification for the hostgroup [CoreServices].

Thank you!

Whew. That was a doozy. Thanks for sticking with me through this post and the series! I hope this content gives you a better understanding of how to integrate your custom applications with SAS Viya and how to do it securely. Remember to evaluate each application's needs and intended use separately to choose the most appropriate type of scoping. Then use the guidance presented here to choose an OAuth flow so your application makes HTTP REST API calls into the SAS Viya endpoints and resources it needs. Feel free to reach out if you have any questions.

Share

About Author

Tara St. Clair

Principal Technical Architect, Field Innovation R&D

Tara develops SAS environments from architectural design, through deployment and security modelling, and ultimately onto administrating and consulting. These days she enjoys writing custom Python serverless functions to extend the value of SAS, integrating them into the platform, and sharing her findings along the way. When she's not doing any of those things, she's tending to her 50+ houseplants.

18 Comments

  1. Thanks for very informative article, i have a question, is authorization code flow, not possible without a man in middle? i.e. without user login to browser. i want to build an app using viya rest api's, but want to use a fixed user as of now. dont want to use client_credentials as doesn't allow to login to UI. is there a way this can be done? and i dont want to use password flow due to obvious reasons

    • Joe Furbee

      Hi Shreyas,
      The authorization code flow requires user interaction.

      My recommendation for this is to generate a refresh token when you create the access token using the auth code. You can use a refresh token longer than the default of 14 days. When you register the client, set refresh_token_validity (expressed in seconds) to a longer amount (90 days max is recommended but you can do longer). Then within your application you can programmatically generate a new access token using the refresh token. This way you would only need to go through the auth code process to generate fresh access (and refresh) tokens after whatever value you use for the refresh_token_validity.

  2. Hi Tara, congrats for this fantastic post series. It helps a lot on developing custom apps on Viya.
    There is just one thing it's not so clear to me. When you register a client you must provide a client_secret. In your print screen you censored it because of security reasons, I guess.
    Here is my doubt. If we have to use the client_secret to get the access_token, we have to write it into the js code and this makes the secret readable by everyone using browser developer tools. Is there a way to keep the client_secret hidden in a javascript front-end app or is the authorization_code flow so secure that it is not a problem to show the client secret in js app code?

    Thank you very much!!!

    • Tara St. Clair
      Tara St. Clair on

      Hey Claudio - thanks for the kind words. It should absolutely be treated like a sensitive piece of information. There are quite a few options, depending on your app framework and environment, for dealing with that securely. For example, you could store the secret in something like Azure Key Vault and make it available to your backend app as Azure discusses in this learning module. But exactly how you handle this depends on a number of factors like how and where you're hosting your app, for example.

  3. Hi, other question: I would like to verify that a JWT token is indeed issued by our SAS Viya system.
    I see that the token is signed with a RSASHA256 signature.
    Where do I get the public key used to sign it in order to verify the signature?
    I tried to explore /opt/sas/viya/config/etc/SASSecurityCertificateFramework without success.

    Thanks!

    • Tara St. Clair
      Tara St. Clair on

      Hi again Edo - unfortunately I don't have experience validating JWT tokens programmatically so I will have to do a little digging and will reach back out to you.

        • Thanks, amazing!
          To whom who may be interested, for example in python using the python-jose package, signature validation is straightforward:

          from jose import jwt
          jwt.decode(token,key,audience="{{CLIENT_ID}}")

          where "token" is the token obtained from Viya, "key" is the json obtained with the GET request to SASLogon/token_keys, and audience is the intended client_id.

          If it works, the token is authentic. If you get an error, the token has been spoofed or is not intended for you!

  4. Hi, for authorization code flow, when configuring "redirect_uri": "https://notviyasite.mycompany.com/callback", everything works but after authentication the redirect is to "https://viya.mycompany.com/callback" which is not the intended url nor it exists.
    This is some kind of security, right? How can I allow "notviyasite" to be a valid redirect domain?

    Thanks!

  5. Bogdan Teleuca on

    Hi Tara, I read your posts and Mike's SGF paper https://www.sas.com/content/dam/SAS/support/en/sas-global-forum-proceedings/2018/1737-2018.pdf
    I am not sure how to interpret the following: see the #

    "scope": ["openid", "*"],
    "resource_ids": ["none"], # it means I cannot interact with any resources? by default blocked, forbidden
    "authorities": ["uaa.admin"], # meaning?
    in
    curl -sk -X POST "${OAUTH_URL}/clients" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $OAUTH_TOKEN" \
    -d '{
    "client_id": "app4",
    "client_secret": "secret",
    "scope": ["openid", "*"],
    "resource_ids": ["none"],
    "authorities": ["uaa.admin"],
    "authorized_grant_types": ["password","refresh_token"],
    "access_token_validity": 600
    }'

    Thank you for your extra clarification.

    • Tara St. Clair
      Tara St. Clair on

      Hi Bogdan - using the value of "none" for the resource_ids property is fine, and it's the default so you can leave it out of your POST here. Our authorization systems are not relying on this property to determine your access. In the example you give, using the password OAuth flow and grant type, that is determined with the scopes property instead. The way you've specified it, your client would allow individual users access to anything they have access to normally, if they'd logged directly into one of the UIs. Instead of the "*" in the list, you can specify a Group or Custom Group instead and then your users would only assume the rights given to that group via any Authorization Rules with that group as the Principal. The authorities property does the same thing for for clients that were registered with the client_credentials grant type which is what I cover in the "General-scoped access tokens" section of this post. Lastly, I would be remiss if I didn't make sure you're aware that the password OAuth flow has user credential combinations flowing over the wire between your client and SAS Viya. Even if SAS Viya is secured over HTTPS, utilizing one of the flows covered in this post would be much more secure. If you need individual user-scoped access for your client, take a look at the authorization_code flow.

  6. Alessandro Mangone on

    Congrats Tara, it's an amazing source of inspiration! I'm building a custom webapp using Django and SAS Viya API. I collected user projects, but I don't understand how to link them in order to redirect the user into the Model Studio in Viya, when he clicks on a specific project. Do you know how to do it? I guess to miss something in the api documentation. Thank you so much, congrats again!

    • Tara St. Clair

      Hi Alessandro, thanks for your comment. We have a /links endpoint that's responsible for generating custom links to specific pieces of content but it's current version only supports linking to SAS Visual Analytics reports and to the SAS BI mobile application. You could link users directly to the /SASModelStudio application which, from a normal user's (non SAS Administrators group member) view, will only display projects that he/she owns or that have been shared with them. If you'd like to email me your use case and company information I'd be happy to put in a feature request for you. Tara.StClair@sas.com

Leave A Reply

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

Back to Top