#1 2015-01-21 17:40:59

jgb
Member
From: Canada
Registered: 2014-11-07
Posts: 7

Adding additional Authorization Scheme(s)

Hi,

First post! Using mORMot I've developed a REST/JSON server for and existing and LARGE product. Our intention is to not only use this mORMot server for the server side of an Android app but also possibly as a general API server.

Over the last few months I've extended mORMot to suite our needs. Because we have a large legacy system many of the built in niceties don't apply and I've had to diverge from a typical mORMot implementation. Fortunately, the framework is written well enough to allow extending the code base to suite our needs without having to make changes to any of the mORMot code.

The use case we have is the "not yet implemented" ORM case where we have externally mapped tables not managed by the framework (see SAD - 1.4.1.9.2. Several ORMs at once, 3rd bullet). I would happily describe in more detail my implementation but that's not why I am posting this...

I'd like to implement OAuth2 authentication, the "Resource Owner Password Credentials Grant" grant type for now(http://tools.ietf.org/html/rfc6749#page-37). The problem I have is that I there is one spot in the mORMot code that could be improved to allow for better authorization scheme extensibility.

I'll show the problem and propose a solution but I am also open to alternatives.

in mormot.pas the method TSQLRestServer.URI has something like this:

  ...
  // 2. handle security
  if (not Ctxt.Authenticate) or
     ((Ctxt.Service<>nil) and
      not (reService in Call.RestAccessRights^.AllowRemoteExecute)) then
     // 401 Unauthorized response MUST include a WWW-Authenticate header,
     // which is not what we used, so we won't send 401 error code but 403
     Call.OutStatus := HTML_FORBIDDEN else
   ...

The problem is, as the comment states, I need to return 401 and add the WWW-Authenticate header. I could/can get around this by adding specialized logic to the TSQLRestServerURIContext descendant I already use for other aspects of my implementation. However, it's not very pretty. Though I haven't actually coded the solution what I'd like to see is:

1) Add a virtual method (e.g. HandleUnauthorized() ) to TSQLRestServerURIContext that sets up Call.OutStatus et al to an appropriate response.
2) The default behavior would be: Call.OutStatus := HTML_FORBIDDEN to be non-breaking
3) Change the above Call.OutStatus := HTML_FORBIDDEN to Ctxt.HandleUnauthorized()

The remaining OAuth2 implementation will go into new TSQLRestServerAuthentication descendants.

I look forward to any feedback.

Regards,
Jeff

Offline

#2 2015-01-21 18:58:13

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,183
Website

Re: Adding additional Authorization Scheme(s)

Does http://synopse.info/fossil/info/cb8d712ec9 fit your needs?

Thanks for sharing!
And welcome on the forum!

Any input, especially of all the mORMot modifications, is welcome.
OAuth support would be a nice add-on, indeed.
big_smile

Offline

#3 2015-01-22 15:26:45

jgb
Member
From: Canada
Registered: 2014-11-07
Posts: 7

Re: Adding additional Authorization Scheme(s)

Yeah, wow! Now that's service.

While it could be that this is all that is needed. I will need to actually implement this first. I'll report back soon....

Thank you for the quick response

Offline

#4 2015-01-22 15:55:18

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,183
Website

Re: Adding additional Authorization Scheme(s)

Great news!
smile

Offline

#5 2015-01-28 05:48:49

jgb
Member
From: Canada
Registered: 2014-11-07
Posts: 7

Re: Adding additional Authorization Scheme(s)

I have a question and sort of thinking out loud regarding the OAuth2 implementation....

First it seems to me that the existing session management does not persist? As I try to plug in the OAuth2 workflow I am looking at RetrieveSession(), SessionCreate() and SessionAccess() to name a few. A session is only created when an /auth call is issued. So, for instance , if the server was restarted all sessions would be invalidated?

In the OAuth2 world session persistence (or Access Token validity) and server persistence are not connected. This is fine for my derived implementation (I store the Access Token in a database table) but does not work well for having a "default" OAuth2 scheme.

For the actual OAuth2 implementation, there is a new abstract class:

  TOAuth2AbstractAuthentication = class(TSQLRestServerAuthentication)
  ...
  end;

OAuth2 has 4 grant types:

  • Authorization Code Grant

  • Implicit Grant

  • Resource Owner Password Credentials Grant

  • Client Credentials Grant

I only expect to support Resource Owner Password Credentials Grant in my project for now. Though it doesn't seem to make sense to try to contribute back to mORMot nothing but a complete implementation.

There are four derived classes to match each grant type:

  // Authorization Code Grant
  // - http://tools.ietf.org/html/rfc6749#section-4.1
  TOAuth2AuthorizationGrant = class(TOAuth2AbstractAuthentication)
  ...
  end;

  // Implicit Grant
  // - http://tools.ietf.org/html/rfc6749#section-4.2
  TOAuth2ImplicitGrant = class(TOAuth2AbstractAuthentication)
  ...
  end;

  // Resource Owner Password Credentials Grant
  // - http://tools.ietf.org/html/rfc6749#section-4.3
  TOAuth2ResourceOwnerPasswordGrant = class(TOAuth2AbstractAuthentication)
  ...
  end;

  // Client Credentials Grant
  // - http://tools.ietf.org/html/rfc6749#section-4.4
  TOAuth2ClientCredentialsGrant = class(TOAuth2AbstractAuthentication)
  ...
  end;

Each of these implement the OAuth2 request/response as per the spec. In my case, I need... well I envision at least 3 different type of user/password validation but for now I'll implement validation against credentials stored in a pre-existing database table.

So for custom authentication and access token generation I use a descendant of the desired grant type like so:

  TMyAuthentication = class(TOAuth2ResourceOwnerPasswordGrant)
  ...
  end;

then in my custom server class (TMyServer = class(TSQLRestServer)) in the constructor:

constructor TMyRestServer.Create(aModel: TSQLModel);
begin
  //Call inherited without registering default authentication
  inherited Create(AModel, False);
  //Register custom authentication, system is designed to support multiple
  AuthenticationRegister([TMyAuthentication]);
  ...
end;

Now in my derived authentication class along with a derived server and custom database access I can validate credentials, generate access tokens and validate access tokens etc.. in a very custom way from my legacy database system.

The AuthenticatioFailed() override you added at my request got a little more complicated.

I added:

  TSQLRestServerAuthentication = class
    ...
    function AuthenticationFailed(Ctxt: TSQLRestServerURIContext): boolean; virtual;
  end;

and then enhanced the new AuthenticationFailed() to push the error handling to each Authentication class

procedure TSQLRestServerURIContext.AuthenticationFailed;
var
  aSession: TAuthSession;
  i: integer;
begin
//JGB
  if Server.HandleAuthentication then
  begin
    Session := CONST_AUTHENTICATION_SESSION_NOT_STARTED;
    Server.fSessions.Lock;
    try
      aSession := nil;
      if Server.fSessionAuthentication<>nil then
        for i := 0 to Server.fSessionAuthentications.Count-1 do
          if Server.fSessionAuthentication[i].AuthenticationFailed(Self) then
            Break;
    finally
      Server.fSessions.UnLock;
    end;
   end;

  // 401 Unauthorized response MUST include a WWW-Authenticate header,
  // which is not what we used, so here we won't send 401 error code but 403
  //JGB - We handle the out status in the TSQLRestServerAuthentication classes
  //JGB - Call.OutStatus := HTML_FORBIDDEN;
end;

This is not finished. It needs to handle multiple authentication schemes better.

Work is ongoing...

As always I look forward to any feedback.

Regards,
Jeff

Offline

#6 2015-01-28 11:38:32

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,183
Website

Re: Adding additional Authorization Scheme(s)

Sessions can be persisted on disk, in an optimized binary file format, thanks to TSQLRestServer.SessionsSaveToFile/SessionsLoadFromFile methods.
See feature request http://synopse.info/fossil/tktview?name=a392945901

I understand that adding OAuth support is not easy, since it is pretty particular, and has several implementation schemes...
Allowing multiple authentications at once makes the mORMot code more complicated, but I think it is a nice-to-have feature.

Feel free to report here any show stopper issue!
Thanks a lot for your efforts!
smile

Offline

#7 2015-01-28 22:24:34

BBackSoon
Member
Registered: 2014-11-15
Posts: 41

Re: Adding additional Authorization Scheme(s)

Just my 2 cents: we've built a REST service with mORMot and a Delphi client to consume it (which works very well) but not a day goes by without a customer asking us if they can consume our REST service via their own web apps (written in PHP, Ruby, Python, ...) and the first thing they ask is OAuth2 support, as the client-side of it comes hassle-free in practically any web application programming language.
I know that, with a little bit of effort, they could implement the current mORMot auth scheme in PHP, Python (etc) but the argument is "why should we do it, when there is a standard that the world is already using (OAuth2) and that our development tools already support amazingly well?"
So, in my humble opinion, OAuth2 support is more than a nice-to-have feature... it's a very desirable one.
Thank you!

Offline

#8 2015-01-29 07:55:06

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,183
Website

Re: Adding additional Authorization Scheme(s)

@BBackSoon
I aggree with you!
OAuth2 is very highly expected!

But my point about "nice-to-have feature" was not OAuth2, but to allow "multiple authentications at once", e.g. the same server allowing at the same time OAuth2 authentication for some clients, and regular mORMot scheme for Delphi clients, and SSPI authentication for Windows clients.

Offline

#9 2015-04-16 20:55:16

jgb
Member
From: Canada
Registered: 2014-11-07
Posts: 7

Re: Adding additional Authorization Scheme(s)

This is working, though not 100% complete. How do I go about contributing this?

The situation is this: there are 4 schemes in the OAuth spec. I only needed one (for now). I have a file that implements the 4 schemes in a way that lets users extend the scheme they want to use and implement security that is appropriate for the situation. The default behavior should validate against the standard mORMot users in a way that's consistent the existing security. I have roughed this in but not tested it (time). Also to be fully complete the mORMot clients should support this, also something I have not done, nor do I have much desire to. I have spent virtually no time working with the mORMot client.

So my delay in contributing this back is these few incomplete items. However, the actually OAuth2 part is all working well. I am sure there will be some rework or that I might have missed something but I think it is certainly ready for those who are interested.

Offline

#10 2015-04-16 22:22:15

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,183
Website

Re: Adding additional Authorization Scheme(s)

Great!

Perhaps you may share a link to test it...

Offline

#11 2015-04-17 06:57:59

Sabbiolina
Member
Registered: 2014-05-20
Posts: 120

Re: Adding additional Authorization Scheme(s)

1

Offline

#12 2015-04-17 16:33:30

jgb
Member
From: Canada
Registered: 2014-11-07
Posts: 7

Re: Adding additional Authorization Scheme(s)

The base implementation is here:

https://gist.github.com/jbendixsen/392e … cation-pas

My pseudo code example of the password grant scheme:

https://gist.github.com/jbendixsen/5b42 … cation-pas

So it occurred to me that I have still yet to do the token expiration logic and refresh token handling. This is definitely something I want to add so I can work on that soon. Still the core functionality is there.

My apologies for not having something more complete.

Offline

Board footer

Powered by FluxBB