All brokers effectively have Parameter UseSession = 1;
But the default value in %CSP.REST is Parameter UseSession As BOOLEAN = 0;
So the developer has to remember to override this every time (or sub-class)

I recommend having an abstract broker, which does technical stuff like CORS, UseSession, Encoding, JSON transformation. All other brokers must extend it (and they don't do technical stuff, only business logic). More on that.

Use same GroupById

If two (or more) web applications share the same GroupBy value, then session opened in one application would be valid in all other applications with the same GroupBy value. This way user needs to login only once (or not at all if we have domain/SSO authentication configured). It's also documented (but hard to find), more docs.

But if it's a third party app then there is no CSP/ZEN app - the use case I have in mind is a 3rd party web developer is creating a complex shop system that needs to communicate with Caché

CSP app could contain only HTML/JS/CSS. So it could be an AngularJS web application, but also hosted with Caché (as a Caché CSP web application).


I have no idea or interest in what technology they are using and it may be that their programming language does not easily support cookies so the CSPCHD (the session cookie) does not get passed.

If you host a web-application via Caché (Ensemble, HealthShare, InterSystems IRIS) then your web application would be authorization-aware automatically. Browser sends relevant cookies/headers with each request so developer don't need to think about it.

I am thinking that in this case the authentication needs to be passed with each Rest call - not an issue
(or use OAUTH which I know little about)

You can do it like this:

  1. Client sends login/pass to some /login REST endpoint and receives token
  2. After that client only sends token with every request
  3. Token should be periodically refreshed

This way you pass login/pass only once instead of with every call. But I'd still recommend Caché security system.

Security (link under construction)
Using JSON - how do you implement a logon?

What are the licensing issues?

For password authenticated web applications it is possible by following these steps:

  1. All brokers effectively have Parameter UseSession = 1;
  2. REST web application and client web application allow only authenticated (i.e. password) access.
  3. REST web application and client web application have reasonable Session timeout (i.e. 900, 3600).
  4. REST web application and client web application have the same GroupById value.
  5. REST web application and client web application have the same cookie path.

If all these conditions are met, user would only consume one license slot per session and perform only one login.

How do you prevent users hacking restful calls that they have no access to?

Authentication as a start, SQL security for basic data access checks, app-level checks for the most specific cases

Sessions or No Sessions 

REST mainly disallows sessions as a mechanism of data transfer. Stateless as stated in REST dissertation by Roy Fielding has three aims:

  • visibility
  • reliability
  • scalability

Visibility is improved because a monitoring system does not have to look beyond a single request datum in order to determine the full nature
of the request. In my opinion that's the most important one and it mainly deals with storing session data in between requests.

Let's say you provide a newsfeed API. There's a lot of news so users get them by pages of 10 elements. Clients can access news in two different ways:

  1. http://host/api/news/next - in this case the server remembers last page of news the client requested and returns the next one. That's not stateless.
  2. http://host/api/news/:pagenmumber - in this case client remembers last page of news he requested and asks for the next one by incrementing page number by 1. That's stateless.

Reliability is improved because it eases the task of recovering from partial failures. Where partial failure is defined as (from Waldo J,
Wyant G, Wollrath A, Kendall S. A Note on Distributed Computing.):

Partial failure is a central reality of distributed computing. Both the local and the distributed world contain components that are subject to periodic failure.

In the case of local computing, such failures are either total, affecting all of the entities that are working together in an application, or detectable by some central resource allocator (such as the operating system on the local machine).

This is not the case in distributed computing, where one component (machine, network link) can fail while the others continue. Not only is the failure of the distributed components independent, but there is no common agent that is able to determine what component has failed and inform the other components of that failure, no global state that can be examined that allows determination of exactly what error has occurred.

In a distributed system, the failure of a network link is indistinguishable from the failure of a processor on the other side of that link.

Sessions as an authentication/authorisation mechanism do not affect Reliability.

Scalability is improved because not having to store state between requests allows the server component to quickly free resources, and
further simplifies implementation because the server doesn't have to manage resource usage across requests. Not relevant in our case as Session still gets created, just destroyed immediately after the request is done.


To sum up: REST APIs can use sessions as authentication mechanism, but not as a data transfer mechanism.

Maybe remove $zf calls?

Here's sample code:

 ClassMethod Test()
{
//Create new Gateway connection object
set gc=##class(%SQLGatewayConnection).%New()
if gc=$$$NULLOREF quit $$$ERROR($$$GeneralError,"Cannot create %SQLGatewayConnection.")
  
//Make connection to target DSN
set pDSN="Cache Samples"
set usr="_system"
set pwd="SYS"
set sc=gc.Connect(pDSN,usr,pwd,0) 
if $$$ISERR(sc) w !, $SYSTEM.OBJ.DisplayError(sc) QUIT 0
if gc.ConnectionHandle="" W !, $$$ERROR($$$GeneralError,"Connection failed") QUIT
  
set sc=gc.AllocateStatement(.hstmt) 
if $$$ISERR(sc) w !, $SYSTEM.OBJ.DisplayError(sc) QUIT 0
  
//Prepare statement for execution
set pQuery= "select Name, DOB from Sample.Person WHERE Name %STARTSWITH ?"
set sc=gc.Prepare(hstmt,pQuery) 
if $$$ISERR(sc) w !, $SYSTEM.OBJ.DisplayError(sc) QUIT 0
//Bind Parameters
set sc=gc.BindParameter(hstmt,1,1,1,12,30,0,30)
if $$$ISERR(sc) w !, $SYSTEM.OBJ.DisplayError(sc) QUIT 0
set var = "A"
set sc=gc.SetParameter(hstmt,$LB(var),1)
if $$$ISERR(sc) w !, $SYSTEM.OBJ.DisplayError(sc) QUIT 0
//Execute statement
set sc=gc.Execute(hstmt)
if $$$ISERR(sc) w !, $SYSTEM.OBJ.DisplayError(sc) QUIT 0
//Get list of columns returned by query
set sc=gc.DescribeColumns(hstmt, .columnlist) 
if $$$ISERR(sc) w !, $SYSTEM.OBJ.DisplayError(sc) QUIT 0
 
//display column headers delimited by ":"
set numcols=$listlength(columnlist)-1 //get number of columns
for colnum=2:1:numcols+1 {
    Write $listget($listget(columnlist,colnum),1),":"
   }
write !
 
//Return first 20 rows 
set sc=gc.Fetch(hstmt)
if $$$ISERR(sc) w !, $SYSTEM.OBJ.DisplayError(sc) QUIT 0
s rownum=1
while((gc.sqlcode'=100) && (rownum<=20)) {
       for ii=1:1:numcols {
       s sc=gc.GetData(hstmt, ii, 1, .val)
       w " "_val
       if $$$ISERR(sc) break
       }
       s rownum=rownum+1
  write !
  set sc=gc.Fetch(hstmt)
if $$$ISERR(sc) break
   }
    
  //Close cursor and then disconnect
set sc=gc.CloseCursor(hstmt)
if $$$ISERR(sc) w !, $SYSTEM.OBJ.DisplayError(sc) QUIT 0
  
set sc=gc.Disconnect()
  
quit sc
}

Check BindParameter method and EnsSQLTypes for SQL types macro definitions. Varchar maybe.

You can also try to call PutData method and similar methods (by passing parts of stream there).