QBoard » Advanced Visualizations » Viz - Tableau » How to implement Tableau Trusted Authentication?

How to implement Tableau Trusted Authentication?

  • 1) Users are prompted to login to Tableau when viewing an embedded dashboard within a web application.

    2) If they close their browser, start a different browser session, or let the Tableau cookie expire, they will be prompted to login again.

    3) Throughout the day, you could potentially be prompted to login multiple times when trying to view dashboards. This quickly becomes annoying and tiresome.

    Tableau offers a solution called "Trusted Authentication" which bypasses the manual login process. After a week of debugging and troubleshooting, I was able to accomplish this. I could not find any solutions on Stackoverflow, so I wanted to share my knowledge on how I accomplished this in hope to help others.

      September 28, 2021 2:00 PM IST
    0
  • Link to Tableau's How Trusted Authentication Works

    How Trusted Authentication Works

    High Level View on how I implemented Trusted Authentication

    1) Tableau server must have an entry to the wgserver.trusted_hosts file with the hostname of your web application for any of this to work.

    2) Three important parameters are passed:

    username          212456449
    server            http://[server]
    target_site       YourTargetSiteName


    3) If the HTTP POST request is valid and the user has the correct Tableau license, Tableau creates a 48 unique character ticket that is only valid for 3 minutes.

    4) I programmatically add the 48 unique character ticket into the embedded JavaScript right before Tableau redeems it.

    How the code works in my web applicatin

    I created a TrustedAuth class that contains two methods: requestTicket() and addTicket(). requestTicket() is an Asynchronous method that takes the three required parameters (sso, server, site). The HTTP POST is fired off and awaits a response. If Tableau response is a -1 , HTTP handshake has failed or the user is invalid. If valid, response will be a 48-character encrypted string.

    addTicket() is a Synchronous method that takes two parameters (ticket, reportLink). This method takes the 48-character encrypted ticket and appends it to the embedded JavaScript (reportLink).

    The web application sends a HTTP GET request to Tableau that includes the embedded JavaScript (reportLink) with the encrypted ticket. Tableau Server redeems the ticket, creates a session, logs the user in, no login prompt dispalyed

    TrustedAuth Class

    public class TrustedAuth
    {
        public async Task<string> requestTicket(int sso, string server, string site)
        {
            try
            {
                //Assign parameters and values
                var values = new List<KeyValuePair<string, string>>();
                values.Add(new KeyValuePair<string, string>("username", sso.ToString()));
                values.Add(new KeyValuePair<string, string>("target_site", site));
    
                //Web Application is HTTP and Tableau is HTTPS, there are certification issues. I need to fake the certs out and return them as true.
                System.Net.ServicePointManager.ServerCertificateValidationCallback = (senderX, certificate, chain, sslPolicyErrors) => { return true; };
    
                //Instantiate HttpClient class
                var client = new HttpClient();
    
                //Encode Content
                var req = new HttpRequestMessage(HttpMethod.Post, server) { Content = new FormUrlEncodedContent(values) };
    
                //POST request
                var res = await client.SendAsync(req);
    
                //Get response value
                var responseString = await res.Content.ReadAsStringAsync();
    
                return responseString;
    
            }
            catch (Exception e)
            {
                System.IO.File.AppendAllText(@"c:\inetpub\wwwroot\WebApplication\TrustedAuthError.txt", ":::ERROR::: " + System.DateTime.Today.ToString() + ":::" + e.ToString() + Environment.NewLine);
                //Add Log4Net logging
            }
    
            return "-1";
    
        }
    
        public string addTicket(string ticket, string reportLink)
        {
            //Add ticket parameter with ticket value. I'm using </object> as my keyword to find and replace
            string addedTicket = reportLink.Replace("</object>", "<param name='ticket' value='" + ticket + "' /></object>");
    
            return addedTicket;
        }
    }

     

    Dashboard Controller

    public async Task<ActionResult> Dashboard(int Report_Num)
        {
         //db will be your database model where your Report_Link is stored
         Report_Completion_Status_NEW report_Completion_Status = db.Report_Completion_Status_NEW.Find(Report_Num);
    
         if (report_Completion_Status == null)
            {
                return HttpNotFound();
            }
    
            var ticket = "";
            //Get Trusted Tableau Authentication Ticket
            try
            {
                //For example purposes, I'm hard-coding the Tableau Server Name and Site Name for the example _trustedAuth.requestTicket method. In my actual code, I'm storing these in my web.config. 
                ticket = await _trustedAuth.requestTicket(b.getSSO(User.Identity.Name), "https://ProdTableauUrlGoesHere.com/trusted", "YourTargetSiteNameHere");
            }
            catch
            {
                ticket = "-1";
            }
    
            //Only add trusted Tableau Authentication ticket if it's valid, else kick user to default Report_Link which will make them login manually. 
            //You get a nasty error message if you pass in a '-1'
            if (!ticket.Equals("-1"))
            {
                ViewBag.Link = _trustedAuth.addTicket(ticket.ToString(), report_Completion_Status.Report_Link);
            }
            else
            {
                ViewBag.Link = report_Completion_Status.Report_Link;
            }
    
            var model = await this.GetFullAndPartialViewModel(Report_Num);
    
            return this.View(model);
        }

     

    New Embedded JavaScript (reportLink) with ticket parameter inserted

    enter image description here

    Dashboard View

    @model WebReportingToolDAL.Models.ViewModels.ReportCategoryListModel
    @{
        ViewBag.Title = "Dashboard";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
    
     <body>
        @Html.Raw(ViewBag.Link)
    </body>

    If all works, you should no longer see the Tableau Login Page.

     

     

      October 11, 2021 12:51 PM IST
    0
  • Step 1: Add a test user

    Create a user on the Tableau Server that you can use to test trusted ticket functionality. See Add Users to Tableau Server. Add that user to a site on the server, and set the user's site role to Explorer.

    Step 2: Create a test HTML page

    Paste the following code into a new .html file that you save on the Tableau Server machine where you're performing the test from. You can change the labels and style attributes as you prefer.

    <html>
    <head>
    <title>Trusted Ticket Requester</title>
    <script type="text/javascript">
      function submitForm(){
        document.getElementById('form1').action =
        document.getElementById('server').value + "/trusted";
      }
    </script>
    <style type="text/css">
      .style1 {width: 100%;}
      .style2 {width: 429px;}
      #server {width: 254px;}
    </style>
    </head>
    <body>
    <h3>Trusted Ticketer</h3>
    <form method="POST" id="form1" onSubmit="submitForm()">
      <table class="style1">
        <tr>
          <td class="style2">Username</td>
          <td><input type="text" name="username" value="" /></td>
        </tr>
        <tr>
          <td class="style2">Server</td>
          <td><input type="text" id="server" name="server" value="https://" /></td>
        </tr>
        <tr>
          <td class="style2">Client IP (optional)</td>
          <td><input type="text" id="client_ip" name="client_ip" value="" /></td>
        </tr>
        <tr>
          <td class="style2">Site (leave blank for Default site; otherwise enter the site name)</td>
          <td><input type="text" id="target_site" name="target_site" value="" /></td>
        </tr>
        <tr>
          <td class="style2"><input type="submit" name="submittable" value="Get Ticket" /></td>
          <td>&#160;</td>
        </tr>
      </table>
    </form>
    <h4>Be sure to add your IP as a Trusted IP address to the server</h4>
    </body>
    </html>

    Step 3: Retrieve a trusted ticket from Tableau Server

    The following procedure will return a trusted ticket from Tableau Server.

    1. Open the web page that you created in the previous step.

      This operation requires JavaScript, so the web browser might prompt you to allow scripts to run.

    2. In the text boxes, enter the following:

      • Username: The test user that was created in Step 1.
      • Server: the address of your Tableau Server, e.g., https://<server_name>.
      • Client IP (optional): The IP address of the user's computer, if it's configured for client trusted IP matching.
      • Site: The name of the Tableau Server site that the test user is a member of.
    3. Click Get Ticket. One of the following will be returned:

      • A unique ticket: A trusted ticket is a string composed of a base64-encoded UUID and a 24-character random string, for example, 9D1OlxmDQmSIOyQpKdy4Sw==:dg62gCsSE0QRArXNTOp6mlJ5.
      • -1: If the value, -1 is returned, the configuration contains an error. See Ticket Value of -1 Returned from Tableau Server.

    Step 4: Test access with trusted ticket

    Now that you have a ticket, you can use it to access content on Tableau Server.

    Construct a URL with the unique ticket that you generated in the previous step to verify access with the trusted ticket. The URL syntax is different if you are accessing a Tableau Server with a single site vs a server that hosts multiple sites.

    This post was edited by Vaibhav Mali at January 5, 2022 2:17 PM IST
      January 5, 2022 2:16 PM IST
    0
  • This is how I did

      [NonAction]
        private static async Task<String> GetTableauStringAsync(string userForTableau)
        {
    
            string postData = "username="+ userForTableau;
            byte[] data = System.Text.Encoding.ASCII.GetBytes(postData);
            var myTicket = "";
    
            try
            {
    
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create("https://myTableauServer.com/trusted");
    
                req.Method = "POST";
                req.ContentType = "application/x-www-form-urlencoded";
                req.ContentLength = postData.Length;
    
                Stream outStream = req.GetRequestStream();
                outStream.Write(data, 0, data.Length);
                outStream.Close();
    
                HttpWebResponse res = (HttpWebResponse)req.GetResponse();
                StreamReader inStream = new StreamReader(res.GetResponseStream());
                string resString = inStream.ReadToEnd();
                inStream.Close();
    
                myTicket = resString;
            }
            catch (Exception ex)
            {
                string exceptionMessage = ex.Message;
                string innerException = ex.InnerException.Message;
    
                myTicket = "ERROR";
            }
    
            return myTicket;
        }

     

    Controller

     [HttpGet]
        public async Task<ActionResult> Index()
        {        
            string resultText = String.Empty;
    
            var task = GetTableauStringAsync(subjectName);
            var result = await task;
            resultText = result;
    
            ViewBag.TableauTicket = resultText ?? " _";
            return View();
        }

     

    Java Script

    @section Scripts {
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script type="text/javascript">
    
        $(document).ready(function () {
                var myTicket = $("#lblTableauTicket").text();
                var patientBKVal = $("#lblPatient_BK").text();
                var destination = "https://myTableauServer.com/trusted/" + myTicket + "/views/MyScorecard_15804618842350/MyScorecard?Patient_BK=" + patientBKVal + "&iframeSizedToWindow=true&:embed=y&:showAppBanner=false&:display_count=no&:showVizHome=no&:origin=viz_share_link";
                window.location.href = destination;
        });
    
    </script>
    
    }

     

     

      September 29, 2021 1:49 PM IST
    0