REST API with Single Sign On

I know there are many posts about the REST API and SSO, but for some reason I am not understanding what I need to do. I am working in VBA. Here is my current code that works with Basic Auth (prompts the user for credentials)

        strUrl = "https://tpa1-eas01/EpicorERPPilot/api/v1/BaqSvc/DataList_Parent_MAX/?PartNumber='" & strPartNum & "'"

    With objRequest
        .Open "GET", strUrl, blnAsync
        .SetRequestHeader "Content-Type", "application/json"
        .Send
        'spin wheels whilst waiting for response
        While objRequest.readyState <> 4
            DoEvents
        Wend
        strResponse = .ResponseText
    End With

What do I do to make this code work with SSO? We have enabled the Windows Authentication in IIS but it is prompting us for credentials but they never are accepted and we end up in an endless loop with the credential window always popping up.

I am hoping for some simple code that works with the SSO.

I’d start with looking at the REST Technical Reference guide (page 85). Epicor provides information about using SSO with the API.
Also make sure your endpoints are in your trusted sites list. Or you’ll get an extra prompt from Windows.

1 Like

I have been reading it, and that has added to my confusion. It says for SSO you need to enable Windows Authentication in IIS (which we have done). We are running under 10.2.600 so we have the proper setting in the web.config file. According to the documentation “This user can now connect to REST services without entering credentials”. However, I still get prompted for credentials.

Where do the endpoints need to be listed as trusted sites? Each computer or on the server?

Each Epicor server would be added to your trusted sites.

We have added the server as a trusted site but I still get the logon prompt. Is there any other way to code the GET to let it know we are using single sign on? Is there a way to get a token without coding in a user’s credentials?

You don’t. You will not be able to use Oauth SSO in VBA without calling an external process.

You could do your auth in powershell and feed it back.

Could you call the token service with a Basic Auth account like so?

Not exaclty SSO but get’s rid of the login prompt.

Option Explicit

Public Const stBaseUrl As String = "https://xxx.epicorsaas.com/xxx/api/v2/odata/xxxxxx"
Public Const stAPIKey As String = "KWwNYuijBwbkc......................................................."
Public Const stCredentials As String = "UserName" & ":" & "Password"
Public stUrl As String
Public stTokenResponse As String
'Public ri As New Dictionary

Sub Button1_Click()
Dim stResponse As String
    stResponse = CallBAQ()
    Range("G4").Value = stResponse
End Sub

Function CallBAQ() As String
Dim http As New MSXML2.XMLHTTP60
Dim stUrl As String, stPostBody, stResponse As String, sSuccess As Boolean

    stUrl = stBaseUrl & "/BaqSvc/baq_PartPrice/Data?PartNum='PL0011000'&$filter=Calculated_PrimarySupplier eq true"

    Set http = CallEpicorAPI("GET", stUrl, "", True, "")

    If http.ReadyState = 4 And (http.Status = 200 Or http.Status = 202) Then
        stResponse = http.responseText
        sSuccess = True
    Else
         stResponse = "Error" & vbNewLine & "Ready state: " & http.ReadyState & _
         vbNewLine & "HTTP request status: " & http.Status & _
         vbNewLine & http.responseText
         sSuccess = False
         MsgBox stResponse
    End If
    
    'Log stAction, stURL, CStr(stPostBody), blnAuthorize, http
    
Exit_Function:
    CallBAQ = stResponse
    Set http = Nothing
End Function

Function CallEpicorAPI(stAction As String, stUrl As String, stPostBody As Variant, blnAuthorize As Boolean, Optional stContentType As String) As MSXML2.XMLHTTP60

    Dim http As New MSXML2.XMLHTTP60
    Dim stResponse As String, sSuccess As Boolean

    http.Open stAction, stUrl, False
    If IsMissing(stContentType) Or 0 = Len(stContentType) Then stContentType = "application/json"
    http.setRequestHeader "Content-Type", stContentType
    http.setRequestHeader "x-api-key", stAPIKey
    If blnAuthorize Then http.setRequestHeader "Authorization", "Bearer " & GetToken()
    http.Send stPostBody

    If http.ReadyState = 4 And (http.Status = 200 Or http.Status = 202) Then
        stResponse = http.responseText
    '    sSuccess = True
    'Else
    '     MsgBox "Error" & vbNewLine & "Ready state: " & http.ReadyState & _
    '     vbNewLine & "HTTP request status: " & http.Status & _
    '     vbNewLine & http.responseText
    '     sSuccess = False
    End If
    
    'Log stAction, stURL, CStr(stPostBody), blnAuthorize, http
    
Exit_Function:
    Set CallEpicorAPI = http
    Set http = Nothing
End Function

Function GetToken() As String
Dim http As New MSXML2.XMLHTTP60
Dim stTokenURL As String, stBase64Credentials As String, stPostBody As String, stResponse As String


    stTokenURL = stBaseUrl & "/Ice.Lib.TokenServiceSvc/GetAccessToken"
 
    stPostBody = "{""clientId"": ""00000000-0000-0000-0000-000000000000"",""clientSecret"": ""string"",""scope"": ""string""}"
    
    stBase64Credentials = EncodeBase64(stCredentials)

    http.Open "POST", stTokenURL, False
    http.setRequestHeader "Authorization", "Basic " & stBase64Credentials
    http.setRequestHeader "x-api-key", stAPIKey
    http.setRequestHeader "Content-Type", "application/json"
    http.Send stPostBody

    If http.ReadyState = 4 And http.Status = 200 Then
        stTokenResponse = http.responseText
        stResponse = ParseJson(stTokenResponse, "AccessToken", "TokenService")
    Else
         MsgBox "Error" & vbNewLine & "Ready state: " & http.ReadyState & _
         vbNewLine & "HTTP request status: " & http.Status & _
         vbNewLine & http.responseText
    End If

Exit_Function:
    GetToken = stResponse
    Set http = Nothing
End Function

Function ParseJson(json As String, stPropName As String, Optional stParseFrom As String = "") As String
    Dim arrStart As Long, arrEnd As Long
    Dim arrJson As String
    Dim key As String
    Dim startPos As Long, endPos As Long
    Dim result As String

    ' If stParseFrom is provided, find the array/object first
    If stParseFrom <> "" Then
        arrStart = InStr(1, json, """" & stParseFrom & """:[", vbTextCompare)
        If arrStart = 0 Then
            ParseJson = ""
            Exit Function
        End If
        arrStart = arrStart + Len(stParseFrom) + 3 ' move past "TokenService":[
        arrEnd = InStr(arrStart, json, "]")
        If arrEnd = 0 Then
            ParseJson = ""
            Exit Function
        End If
        arrJson = Mid(json, arrStart, arrEnd - arrStart)
    Else
        arrJson = json
    End If

    key = """" & stPropName & """" & ":"
    startPos = InStr(1, arrJson, key, vbTextCompare)
    If startPos = 0 Then
        ParseJson = ""
        Exit Function
    End If

    startPos = startPos + Len(key)
    ' Skip spaces and quotes
    Do While Mid(arrJson, startPos, 1) = " " Or Mid(arrJson, startPos, 1) = """"
        startPos = startPos + 1
    Loop

    endPos = InStr(startPos, arrJson, """")
    If endPos = 0 Then
        ParseJson = ""
        Exit Function
    End If

    result = Mid(arrJson, startPos, endPos - startPos)
    ParseJson = result
End Function

Function EncodeBase64(text As String) As String
    Dim arrData() As Byte
    arrData = StrConv(text, vbFromUnicode)
    Dim objXML As Object
    Dim objNode As Object
    Set objXML = CreateObject("MSXML2.DOMDocument")
    Set objNode = objXML.createElement("b64")
    objNode.DataType = "bin.base64"
    objNode.nodeTypedValue = arrData
    EncodeBase64 = Replace(objNode.text, vbLf, "")
    Set objNode = Nothing
    Set objXML = Nothing
End Function

Function CallMSGraphAPI(stAction As String, stUrl As String, stPostBody As Variant, blnAuthorize As Boolean, Optional stContentType As String) As MSXML2.XMLHTTP60

    Dim http As New MSXML2.XMLHTTP60
    Dim stResponse As String, sSuccess As Boolean

    http.Open stAction, stUrl, False
    If IsMissing(stContentType) Or 0 = Len(stContentType) Then stContentType = "application/json"
    http.setRequestHeader "Content-Type", stContentType
    If blnAuthorize Then http.setRequestHeader "Authorization", "Bearer " & GetMSToken()
    http.Send stPostBody

    If http.ReadyState = 4 And (http.Status = 200 Or http.Status = 202) Then
        stResponse = http.responseText
    '    sSuccess = True
    'Else
    '     MsgBox "Error" & vbNewLine & "Ready state: " & http.ReadyState & _
    '     vbNewLine & "HTTP request status: " & http.Status & _
    '     vbNewLine & http.responseText
    '     sSuccess = False
    End If
    
    'Log stAction, stURL, CStr(stPostBody), blnAuthorize, http
    
Exit_Function:
    Set CallMSGraphAPI = http
    Set http = Nothing
End Function

1 Like

I have been digging and digging and have not been able to find a way to get SSO to work with REST. I found this for tokens in the Epicor REST guide:

private static string ObtainToken()
 {
 HttpClient client = new HttpClient();
 // Add an Accept header for JSON format.
 client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHea
derValue("application/json"));
 try
 {
 const string TokenCreateURL = "https://EpicorServer/ERP102400/T
okenResource.svc/";
 var request = new HttpRequestMessage()
 {
 RequestUri = new Uri(TokenCreateURL),
 Method = HttpMethod.Post,
 };
 //send user credential
 request.Headers.Add("username", "manager");
 request.Headers.Add("password", "manager"); 

The last 2 lines here require a username and password, which defeats the purpose of SSO. Even to get a token you need a username and password.

We are moving to SSO and I need to get this working. Does anyone know how to just pass the user information (in our case their email address is used for SSO) without a password? I don’t mind having to get a token and then sending the token for my queries but I need to somehow get past any method that uses the password.

I have no idea because I’m not on prem so Windows sso is not an option. However, afaik, iis with windows auth turned on also requires browser cooperation to handle token flow (who knows if that’ll be honored by excel or wherever you’re running vba) but browser-side settings typically go like:

• Add your site to the Intranet zone.
• Enable automatic logon in browser settings or via Group Policy:
• Computer Configuration > Administrative Templates > Windows Components > Internet Explorer > Internet Control Panel > Security Page > Intranet Zone

You could confirm this is setup and working by hitting an api endpoint in your browser, but again who knows if it’ll work elsewhere.

I’m not aware of an api token flow that doesn’t require user/password nore one that uses windows auth outside the browser ‘automatic logon’ magic.