Hi all,
It is not clear to me from the beta documentation how the authentication works with the Mobile application.
The documentations seems to suggest that the restserver does the username/password validation and creates a sealed client-principal, then the restserver passes that sealed client-principal to the appserver.
So how does the restserver seal a client-principal? Does it know about domains and domainkeys? It does not even have a databaseconnection to load domains from.
During a multi-tenancy workshop we have learned that the best-practice is to have an STS (Secure Token Service) (behind extra firewalls so it cannot be reached from the web), and this STS is the only server who has access to the username/password and is also the only server who is allowed to seal a client-principal.
Thanks,
Jurjen Dijkstra.
My apologies for what is going to be a long post, although hopefully a useful one.
It is not clear to me from the beta documentation how the authentication works with the Mobile application.
The documentations seems to suggest that the restserver does the username/password validation and creates a sealed client-principal, then the restserver passes that sealed client-principal to the appserver.
So how does the restserver seal a client-principal? Does it know about domains and domainkeys? It does not even have a databaseconnection to load domains from.
During a multi-tenancy workshop we have learned that the best-practice is to have an STS (Secure Token Service) (behind extra firewalls so it cannot be reached from the web), and this STS is the only server who has access to the username/password and is also the only server who is allowed to seal a client-principal.
Firstly, setup on the server.
The OE Web Server (to give it its formal name, aka the REST server, aka Tomcat) performs authentication for a request. All requests to the service must be authenticated, even if it's an anonymous authentication.
The authentication performed in the OE Web Server using the Spring framework's authentication framework. There are a number of authentication models in the box, with the 11.2 release. They are in the web.xml file that the tooling generates for you (in ${project}/RESTContent\WEB-INF\web.xml).
contextConfigLocation
/WEB-INF/appSecurity-anonymous.xml
Each of the security models has their own XML file (as you can gather from the above). If we look at the appSecurity-form-local.xml example, we can see some XML like the below. This tells us what performs the actual authentication: in this case, it's a file called users.properties. The request's user is authenticated against this file in this case, and a Spring token is created for that user.
For anonymous, it looks something like the below.
Each of those models has an option to create an ABL Security Token - a Client-Principal object - with some hard and soft-coded values from the Spring token.
class="com.progress.rest.security.OEClientPrincipalFilter" >
If the enablecp property above is true, an C-P is created by the REST manager and passed into the ABL session; this is accessible via the GetClientPrincipal() method (new in 11.2) on the SESSION:CURRENT-REQUEST-INFO . The details of the values you can specify for the above should be in the doc somewhere.
Once you have that C-P in your activate procedure, you can assert the user via the SECURITY-POLICY:SET-CLIENT() or SET-DB-CLIENT() methods.
So this works with the best practices you laid out: a STS authenticates a request, creates a client-principal and passes it to the ABL AppServer session. However, as you noted, there's a missing component here: the ability to use an ABL authentication realm as a STS.
This is something that we know is missing, and are working on. If this is important to you, please let us know, so that we make sure it gets the appropriate attention.
You will need to work around this in 11.2. You will need to expose your ABL STS via its own REST service, and manually pass around the token. This is somewhat analogous to how you had to add a parameter for a session or context ID to your calls to State-free AppServers prior to OE 11.0.0.
So you'll perform the Login operation , passing in username, password, domain etc, and getting the session id back. The ABL STS will authenticate the credentials, create and seal a client-principal and pass back a token (probably just the session ID). When next you make a request to a business logic service, you'll have to add that session/user context ID as part of the request. This token won't be available via SESSION:CURRENT-REQUEST-INFO in the activate procedure. The ABL business logic would need to call a GetClientPrincipal() method in the Security Token Service, which would then be set in the session. Note that even if you use the web server's client-principal, that will not be the same client-principal that the STS would create. As you mention, the domain keys are not the same.
On the client side, you would need to perform two logins, and establish two sessions: one for the OE Web Server and one for the ABL app (via STS).
When your mobile app loads, you need to do establish a session, similar to the code below. Note that in this case, I'm using the anonymous authentication model. I've removed a lot of validation etc. In my example, I have one service for business data (VehicleOrderService_*) and one service for the STS (SecurityTokenService_*). The pdsession.login() in this Javascript is for the OE Web Server. The *_Settings objects are service settings created by the Mobile App Builder.
You could, of course, have different OE Web Server logins for the different services, but that doesn't alter what I'm trying to illustrate. The line with AETF.context.sessions[] in it is storing the session locally (in the client) so that it's not GC'ed.
var settings = [VehicleOrderService_ShoppingCartService_Settings,
SecurityTokenService_SecurityTokenService_Settings
];
for
(
var
i = 0; i
var
pdsession = AETF.context.sessions[settings[i]
.RESTapplicationURL];
var
loginResult = pdsession.login(
settings[i].RESTapplicationURL,
'', //user
''); //password
pdsession.addCatalog(settings[i].catalogURL);
};
Assuming the login succeeds. your app can go about its business - the client can now access the ABL services you've exposed, including the STS.
Your login screen can now pass the user/password/domain through to the STS in paramters/arguments and will get back the session token too.
Does it know about domains and domainkeys? It does not even have a databaseconnection to load domains from.
One thing to bear in mind when thinking about all of this is that the OE Web Server is expected to be running in "hostile territory", and the STS in "safe country". You may want to consider having separate domains for your webserver login and for your STS/Business logic.
Apologies for the long post, and I hope that it makes things a little clearer,
-- peter
Hi Peter,
Thanks for answer and I really appreciate that it's a long post. A shorter post would have been harder to understand, I am glad you took the time and effort to elaborate.
But now I am confused and stuck with 2 questions.
The first question is:
When the ABL STS is exposed with its own REST service, and this REST service is another instance of an OE Web Server, and the Mobile client logs in to that OE Web Service using pdsession.login(,,,), then the question remains how THAT webservice is using the ABL STS to validate password and seal a C-P. It seems to me that the question is moved from one REST service to another, but still not actually using the ABL STS.
Perhaps I am mis-interpreting your explanantion, perhaps we should just have a session.cls with a session:login(username,password) on the appserver and invoke it from the mobile client, while that session.cls in turn calls the ABL STS for the actual authentication and sealing. But if that's the case then I don't quite understand why you would have two sessions on the mobile client.
The second question is about "When next you make a request to a business logic service, you'll have to add that session/user context ID as part of the request.".
How can I do that... since the BusinessObject CRUD operations have a fixed signature with only 1 parameter?
Thinking about it a bit more... is the following a correct scenario?
I start the mobile app and it does an anonymous login at the OE Web Server, the OE Web Server creates a unique client-principal for my session and this CP is passed to every ABL call in SESSION:CURRENT-REQUEST-INFO.
if the client-principal in SESSION:CURRENT-REQUEST-INFO really identifies the session (and is never used by an other mobile user, even though its an anonymous session), then the ABL can at least recognize me and use that CP as a key to fetch the "real" authenticated CP from STS.
If that scenario works, then it does not really matter how the OE Web Server makes a client-principal, it only has to be unique and constant for the lifetime of the session. Heck it does not even have to be a CP, just a SessionId GUID will do the trick.
Thanks,
Jurjen.
The first question is:
When the ABL STS is exposed with its own REST service, and this REST service is another instance of an OE Web Server, and the Mobile client logs in to that OE Web Service using pdsession.login(,,,), then the question remains how THAT webservice is using the ABL STS to validate password and seal a C-P. It seems to me that the question is moved from one REST service to another, but still not actually using the ABL STS.
Perhaps I am mis-interpreting your explanantion, perhaps we should just have a session.cls with a session:login(username,password) on the appserver and invoke it from the mobile client, while that session.cls in turn calls the ABL STS for the actual authentication and sealing. But if that's the case then I don't quite understand why you would have two sessions on the mobile client.
You can certainly expose the STS in the same service as the BL, but that would remove the ability to secure the STS more securely (with SSL/TLS say).
With the current situation and using an ABL STS, the flow would go something like the below. Note that lots of this may change.
The important thing here is that the Business Logic service knows how to talk directly to the STS service. The BL service also has similar domains as the STS (similar because the domain name and keys must be indentical between them, but nothing else needs to be).
The second question is about "When next you make a request to a business logic service, you'll have to add that session/user context ID as part of the request.".
How can I do that... since the BusinessObject CRUD operations have a fixed signature with only 1 parameter?
My approach was to do what I outline in this post (http://communities.progress.com/pcom/message/167878#167878). I create a JSON object on the client and pass it to the server. The conversation Mike and I had at http://communities.progress.com/pcom/message/167892#167892 has some more info and techniques. The create-your-own-service approach is very advanced so you may not want to use that as a first or second approach .
Thinking about it a bit more... is the following a correct scenario?
I start the mobile app and it does an anonymous login at the OE Web Server, the OE Web Server creates a unique client-principal for my session and this CP is passed to every ABL call in SESSION:CURRENT-REQUEST-INFO.
if the client-principal in SESSION:CURRENT-REQUEST-INFO really identifies the session (and is never used by an other mobile user, even though its an anonymous session), then the ABL can at least recognize me and use that CP as a key to fetch the "real" authenticated CP from STS.
If that scenario works, then it does not really matter how the OE Web Server makes a client-principal, it only has to be unique and constant for the lifetime of the session. Heck it does not even have to be a CP, just a SessionId GUID will do the trick.
If you're using the anonymous model a new C-P is created for each request, but in principal that's right.
Assuming the OEWS can use an ABL STS for an authentication provider, the flow would be different to the above, and closer to what you suggest:
The important thing here is that (as above) the BL service also has similar domains as the STS (similar because the domain name and keys must be indentical between them, but nothing else needs to be).
Hopefully this helps clear up things some more ...
-- peter
method protected JsonObject ParseFilter (input pcFilter as character):
define variable oParsedConstruct as JsonConstruct no-undo.
define variable oParser as ObjectModelParser no-undo.
oParser = new ObjectModelParser().
oParsedConstruct = oParser:Parse(pcFilter).
return cast(oParsedConstruct, JsonObject).
end method.
@openapi.openedge.export(type="REST", useReturnValue="false", writeDataSetBeforeImage="false").
@progress.service.resourceMapping(type="REST", operation="read", URI="?filter=~{filter~}", alias="", mediaType="application/json").
method public void ReadBrandDataService(input filter as character, /* brandName, optional-VehicleId */
output dataset dsVehicleOptions):
define variable oFilter as JsonObject no-undo.
define variable cBrand as character no-undo.
define variable cVehicleId as character no-undo.
define variable iLoop as integer no-undo.
define variable oOptionTypes as JsonArray no-undo.
define variable cTypes as character extent no-undo.
define buffer lbTenant for Tenant.
dataset dsVehicleOptions:empty-dataset().
oFilter = ParseFilter(filter).
if not oFilter:Has('brandName') then
undo, throw new AppError('Filter has no brandName property').
cBrand = oFilter:GetCharacter('brandName').
/* do work */
end method.
Thanks for the incredible help Peter
Agreed - Peter is this the making of a white paper? ;-)
Peter, firstly many thanks for the valuable info. It has been very useful to me.
Is there any information out there about writing custom security models? Specifically, what I want to be able to do is take the username and password from an HTTP basic authentication header and validate them against our OpenEdge database, rather than against the users.properties file.
Thanks,
John
We are working on a solution for customizing the web application's authentication process by using an OpenEdge AppServer as a source of user account information. The end result of the web application's authentication process is the same, which is if the authentication is a success a Client-Principal will be created, sealed, and sent to the AppServer on each of the client's requests. If you can envision replacing the users.properties file in a BASIC or Form authentication configuration with an AppServer you understand the basic concept.
As a developer you can write the AppServer ABL to supply user account information from any source you choose.
The Client-Principal creation will always allow you to optionally specify an OE domain name instead of the default 'blank' domain name. The OE domain's access code in the web application will also be configurable so that it matches the AppServer domain's access code. (Otherwise what would be the point of using Domains)
Would this type of solution be adequate for your needs? Or do you require direct physical access to user accounts in an OpenEdge database?
Mike Jacobs
Thank you for your message. I am currently out of the office, with no access to e-mail.
I will be returning on Monday 20th May.
Best regards,
John
Hi Mike. Thank you for the response. I'm still trying to get my head around this stuff, so forgive me if I sound a bit muddled.
mjacobs wrote:
We are working on a solution for customizing the web application's authentication process by using an OpenEdge AppServer as a source of user account information.
That sounds exactly like what I need to do! We have a database table containing username & (encoded) password for each user. This is what I want to authenticate against. Do you know when the solution you mention above will be available? If there's any documentation on this available, I'm happy to plug away myself, but I haven't managed to find anything so far.
Or do you require direct physical access to user accounts in an OpenEdge database?
I think the answer's yes. But I'm happy enough for that to be by an AppServer. Or have I misunderstood?
Thanks,
John
Hi John,
The functionality we're talking about here is 'targeted' for the 11.3 release of OpenEdge. After saying that, I am required to caution you that problems can occur at any stage of a features development and disqualify it from making it into the initial targeted release.
Because the documentation is not written yet, there is nothing that I can give you. However, I can tell you some things that may be of help:
. The source of user account information is a State-free AppServer running a Singleton ABL class that implements a built-in OE OOABL interface
. Two web application security template configurations (for BASIC & Form) will be supplied to help you configure how the web application
. Connects to the AppServer
. Constructs and seals a Client-Principal that is passed to the AppServer hosting the REST/Mobile service
. The web application's authenticaiton service will query the AppServer for:
. The existence of a user account ( which can optionally include an OE domain name)
. The account's enabled state
. The account's locked state (optional)
. The account's expired state (optional)
. The roles the user account was granted membership in (optional)
. Whether the client supplied password matches the user account's password
. On success the web application's security service will generate a sealed Client-Principal that will later be passed to an AppServer and used for setting user ids or SSO to other OE products
The other option of using the built-in JDBC authentication provider with a SQL92 served OpenEdge database probably would not work due to using the 'ENCODE' password storage. If the password storage was in a more common hashed format and encoding you could *maybe* generate the SQL to query the account and role information. So I would not follow that path just yet.
Hope this helps,
Mike Jacobs
Hi Mike, that does help. Thank you. I guess I'll hang on for the 11.3 release.
John
I see there's an early release of OpenEdge 11.3 out. I can't immediately see the functionality we're talking about (custom authentication) in the list of enhancements. Is it included? Is it listed? Am I just looking for the wrong words? One more question: if it's not in the early release, is it still targetted for the final release?
Thanks,
John
John,
For one reason or another, the Single Point of Authentication (SPA) for REST, Mobile, and BPM was not published in the early access. It will be present in the release version of 11.3. Are you currently working with, or will be working with a copy of the early release?
Mike Jacobs
Hi Mike,
No, not using the early release. I just saw that it was out there and had a look at the release notes out of interest. I'll be keen to get my hands on the final release, though!
John
Hi Mike - are there plans to provide "demo" code to exercise the SPA in 11.3?
There is a sample for the BPM SPA supplied in 11.3's samples directories. It works basically the same way for both BPM & REST. The sample is based on using the _user table in a database, but the ABL implementation in the AppServer could return user account information from any source it chose.
Mike Jacobs
Hi all,
Just wanted to let you know we created an article in our knowledge base so others can benefit from this thread.
KB article number: 000041784...
mjacobs wrote:
There is a sample for the BPM SPA supplied in 11.3's samples directories. It works basically the same way for both BPM & REST. The sample is based on using the _user table in a database, but the ABL implementation in the AppServer could return user account information from any source it chose.
Mike Jacobs
Excellent!