Question
· Feb 8, 2023

Implementing basic auth on a rest API

Recently i've been using Restforms2 to create a CRUD API for a project. But it lacks some advanced functionality that we need, so we have created a production with a REST WS which handles those advanced methods. That works great but there's a drawback, it does not have authentication. 

I would want to use the same authentication method as Restforms2 which is a basic auth using IRIS users and passwords. 

Searching for this, i have found a similar topic. It uses $SYSTEM.Security.Login(user, pass) in a similar manner to create a token. This works flawlessly if you use an account with all privileges... mostly. This method switches the user which runs the code. So if you try to login in the api with an unprivileged user, the next time it will try to run the code as that user and fail. 

Is there a similar method to $SYSTEM.Security.Login(user, pass) that only does check if the login would have worked but does not actually switch the current user?

Is there any other workaround?

Thanks

 

Product version: IRIS 2022.1
Discussion (4)2
Log in or sign up to continue

Which Business Service class are you using?

How is the produciton receiving the message?

Usually for REST services on a production, I do the following:

  • Create a "dispatch" class with the basic which extends from %CSP.REST
  • This REST class will receive the messages, then invoke a business service in the production.
  • You set up a CSP application that uses this class as the dispatcher.
    • Set it as authenticated, which will use basic authentication.

Something like the below. This is in one class, but you can put it into two. You then use this class as the dispatch class on the CSP application:

Class MyPackage.RESTService Extends (%CSP.REST, Ens.BusinessService)
{

Parameter UseSession = 0;

/// Name of Ensemble Business Service. Set in implementation
Parameter BUSINESSSERVICENAME = "Production Clever Methods";

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
	<Route Url="/somemethod Method="POST" Call="HandleSomeFancyMethod"/>
</Routes>
}



ClassMethod HandleSomeFancyMethod() As %Status
{
	set sc = $$$OK
	try {
		// Check Content Type
		if (%request.ContentType '= "application/json") {
			// Throw some error here or repond
		}
		set %response.ContentType = "application/json"

		// Check Data Recieved
		if (%request.Content = "") {
			// Empty data error or "bad request or something"
		}

		// Parse the input into a Dynamic Object or whatever and validate as you'd like. You can also just send the stream to the service as indicated further down

		// Create a business service
		set sc = ##class(Ens.Director).CreateBusinessService(..#BUSINESSSERVICENAME, .tService)
		if $$$ISERR(sc) {
			// throw some error
		}

		// Create input for Service
		set tEnsRequest = ##class(Ens.StreamContainer).%New()
		do tInput.Rewind()
		set sc = tEnsRequest.StreamSet(tInput)
		if $$$ISERR(sc) {
			// trhow some error
		}

		Set tAttrs=##class(%ArrayOfDataTypes).%New()
		do tAttrs.SetAt(%response.ContentType,"ContentType")
		do tEnsRequest.SetAttributes(.tAttrs)

		// Process the input
		set sc = tService.ProcessInput(tEnsRequest, .tEnsOutput)
		// handle the sc however you see fit
		set sc = tEnsOutput.StreamGet().OutputToDevice()
		// handle the sc however you see fit

	} catch tEx {
		// error 500
	}

	quit sc
}

Method OnProcessInput(pInput As Ens.StreamContainer, Output pOutput As Ens.StreamContainer) As %Status
{
	set sc = $$$OK
	try {
		// do whatever you want to do
		// You can send to other business hosts and so forth


		// Set the response object into the stream
		set tStream = ##class(%Stream.GlobalCharacter).%New()
		// tDynamicObj in this case is the reponse object
		set sc = tStream.Write(tDynamicObj.%ToJSON())
		set sc = pOutput.StreamSet(tStream)
	} catch ex {
		set sc = ex.AsStatus()
	}

	quit sc
}

}

Using the proposed approach, there will not be a port listening for REST messages on the Production.

All WS requesst will have to go through the CSP gateway.
If there are other Business Operations utilising that REST service, you will have to connect via the web-server and set up credentials to use.

Also, as suggested by @Dmitry Maslennikov , you can split the services into smaller services if control is required at that level.

Then for local services consumed by the same or other productions pn the same server, you can use Ens RESTService, and control access to the port on the OS firewall to only allow only for traffic from localhost.

Also have a look at the class documentation.
You can disable the local port, so that it will not listen from the production and enable it to go via CSP. You then need to set the CSP application authentication options.
From the class documentation.
property EnableStandardRequests as %Boolean [ InitialExpression = 1 ];

Listen via the CSP WebServer in addition to listening on the HTTP.InboundAdapter's custom local port, if the Adapter is defined/

Note that SSLConfig only applies to the custom local port. To use SSL via the CSP WebServer, you must configure the WebServer separately.

If the Service is invoked via the CSP WebServer, the ?CfgItem= URL parameter may be used to distinguish between multiple configured same-class Services but the standard csp/namespace/classname URL must be used.

Well, if you require some parts of API to be anonymously available, and another part requires authorization. Then the easiest way is to implement two these APIs separately,  and configure Web Applications this way, where one of them will have anonymous access, and another would require password. And it will work, and you will not need to do anything else.

But if have everything in on API, you can look at my demo project. Based on Realworld application, offereded realizations in many different languages and frameworks.