Rest Server Authentication using JWT (JSON Web Token)

Bookmarks: 

Friday, May 06, 2016

When building HTTP Servers with TMS Business frameworks - more specifically Rest/JSON servers with TMS XData - you can use the builtin authentication and authorization mechanism to protect server resources/requests. Such mechanism is actually implemented in our lower-level framework TMS Sparkle and can be used for any types of HTTP server, not only XData. This blog post will show you how to use authorization and authentication using JSON Web Tokens.

JSON Web Token (JWT)

From Wikipedia: "JSON Web Token (JWT) is a JSON-based open standard (RFC 7519) for passing claims between parties in web application environment". That doesn't say much if we have never heard about it before. There is plenty of information out there to read more in details about JWT, so here I'm going directly to the point in a very summarized practical way.

A JWT is a string with this format:

aaaaaaaaaaa.bbbbbbbbbb.cccccccccc

It's just three sections in string separated by dots. Each section is a text encoded using base64-url:

(base64url-encoded header).(base64url-encoded claims).(base64url-encoded signature)

So a real JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidG1zdXNlciIsImlzcyI6IlRNUyBYRGF0YSBTZXJ2ZXIiLCJhZG1pbiI6dHJ1ZX0.
pb-4JAajpYxTsDTqWtgyIgpoqCQH8wlHl4RoTki8kpQ

If we decode each part of the JWT separately (remember, we have three parts separated by dots), this is what we would have from part one (spaces and returns added to make it more readable). It's the header:

{
  "alg":"HS256", 
  "typ":"JWT"
}

And this is part two decoded, which is the payload or claims set:

{
  "name":"tmsuser",
  "iss":"TMS XData Server",
  "admin":true
}

Finally the third part is the signature. It makes no sense to decode it here since it's just a bunch of bytes that represent the hash of three things: the header, the payload, and the secret that only the generator of the JWT knows.

The payload is the JSON object that "matters", it's the actual content that end-user applications will read to perform actions. The header contains meta information of the token, mostly the hashing algorithm used to generate the signature, also present in the token. So, we could say that a JWT is just an alternative way to represent a JSON object, but with a signature attached to it.

What does it has to do with authentication and authorization? Well, you can think of the JWT as a "session" or "context" for an user accessing your server. The JSON object in the payload will contain arbitrary information that you are going to put in there, like permissions, user name, etc. This token will be generated by your server upon some event (for example, an user "login"), and then the client will resend the token to the server whenever he wants to perform any operation. This would be the basic workflow:

1. Client performs "login" in the server by passing regular user credentials (user name and password for example)
2. The server validates the credentials, generates a JWT with relevant info, using the secret, and sends the JWT back to the client
3. The client sends the JWT in next requests, passing the JWT again to the server
4. When processing each request, the server checks if the JWT signature is valid. If it is, then it can trust that the JSON Object in payload is valid and can proceed normally

Since only the server has the secret, there is no way the client can change the payload, adding false information to it - for example, change the user name or permissions. When the server receives the modified JWT, the signature will not match and the token will be rejected by the server.

For more detailed information on JSON Web Tokens (JWT) you can refer to https://jwt.io, the Wikipedia article or just the official specification. It's also worth mentioning that for handling JWT internally, either to create or validate the tokens, TMS XData uses under the hood the open source Delphi JOSE and JWT library.

JWT Authentication with TMS XData

Enough of theory, the next steps will show you how to implement authentication/authorization using JWT in TMS XData. This is just a suggestion of implementation, and it's up to you to define with more details how your system will work. In this example we will create a login service, add the middleware and use XData server-side events to implement authorization.

User Login and JWT Generation

We're going to create a service operation to allow users to perform login. Our service contract will look like this:

  [ServiceContract]
  ILoginService = interface(IInvokable)
  ['{BAD477A2-86EC-45B9-A1B1-C896C58DD5E0}']
    function Login(const UserName, Password: string): string;
  end;

Users will send user name and password, and receive the token. Delphi applications can invoke this method using the TXDataClient, or invoke it using regular HTTP, performing a POST request passing user name and password parameters in the body request in JSON format. Nevertheless, be aware that you should always use a secure connection (HTTPS) in your server to protect such requests.

The implementation of such service operation would be something like this:

uses {...}, Bcl.Jose.Core.JWT, Bcl.Jose.Core.Builder;
 
function TLoginService.Login(const UserName, Password: string): string;
var
  JWT: TJWT;
  Role: string;
  IsAdmin: Boolean;
begin
  { check if UserName and Password are valid, retrieve User data from database, 
   add relevant claims to JWT and return it. In this example, we will only
   add two claims: Role and IsAdmin.  }
  
  // Now that application specific logic is finished, generate the token 
  JWT := TJWT.Create(TJWTClaims);
  try
    JWT.Claims.SetClaimOfType<string>('role', Role);
    JWT.Claims.SetClaimOfType<string>('isadmin', IsAdmin);
    JWT.Claims.Issuer := 'XData Server';
    Result := TJOSE.SHA256CompactToken('secret', JWT);
  finally
    JWT.Free;
  end;
end;

Now, users can simply login to the server by performing a request like this (some headers removed):

POST /loginservice/login HTTP/1.1
content-type: application/json
 
{
  "UserName": "tmsuser",
  "Password": "tmsuser"
}

And the response will be a JSON object containing the JSON Web Token (some headers removed and JWT modified for better readibility):

HTTP/1.1 200 OK
Content-Type: application/json
 
{
    "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6InRtc3VzZXIiLCJpc3MiOiJYRGF0YSBTZXJ2ZXIifQ.CAxxa3aizZheG3VXmBoXtfdg3N5jN9tNAZHEV7R-W4Q"
}

For further requests, clients just need to add that token in the request using the authorization header by indicating it's a bearer token. For example:

GET /customers?$orderby=Name HTTP/1.1
content-type: application/json
authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6InRtc3VzZXIiLCJpc3MiOiJYRGF0YSBTZXJ2ZXIifQ.CAxxa3aizZheG3VXmBoXtfdg3N5jN9tNAZHEV7R-W4Q

Adding TJwtMiddleware to process tokens in requests

The second step is to add the TJwtMiddleware to our XData server module. It's done just once, before starting up the server with the module. All we need to inform is the secret our middleware will use to validate the signature of the received tokens:

uses {...}, Sparkle.Middleware.Jwt;
 
{...}
  Module.AddMiddleware(TJwtMiddleware.Create('secret'));

That's it. What this will do? It will automatically check for the token in the authorization header. If it does exist and signature is valid, it will create the IUserIdentity interface, set its Claims based on the claims in the JWT, and set such interface to the User property of THttpRequest object. Regardless if the token exists or not and the User property is set or not, the middleware will forward the processing of the request to your server anyway. It's up to you to check if user is present in the request and perform the correct actions. The only situation where the middleware will return immediately is if the token is present and is invalid (bad format or wrong signature). In this case it will return an error to the client immediately and your server code will not be executed.

Authorizing the requests

Finally, in your application code, all you have to do is check for such User property and take actions based on it. For example, suppose you have a service operation DoSomething that runs some arbitrary code. You don't want to allow anonymous requests (not authenticated) to execute that operation. And you will also only execute it if authenticated user is an administrator. This is how you would implement it:

uses {...}, Sparkle.Security, XData.Sys.Exceptions;
 
procedure TMyService.DoSomething;
var
  User: IUserIdentity;
begin
  User := TXDataOperationContext.Current.Request.User;
  if User = nil then
    raise EXDataHttpUnauthorized.Create('User not authenticated');
  if not (User.Claims.Exists('isadmin') and User.Claims['isadmin'].AsBoolean) then
    raise EXDataHttpForbidden.Create('Not enough privileges');
  
  // if code reachs here, user is authenticated and is administrador
  // execute the action
end;

Server-side events

The above code is valid for service operations. But you can also use server-side events to protect the entity sets published by XData. For example, you can use the OnEntityDeleting event to not allow non-admin users to delete resources. The event handler implementation would be pretty much the same as the code above (Module refers to a TXDataServerModule object):

  Module.Events.OnEntityDeleting.Subscribe(procedure(Args: TEntityDeletingArgs)
    var User: IUserIdentity;
    begin
      User := TXDataOperationContext.Current.Request.User;
      if User = nil then
        raise EXDataHttpUnauthorized.Create('User not authenticated');
      if not (User.Claims.Exists('isadmin') and User.Claims['isadmin'].AsBoolean) then
        raise EXDataHttpForbidden.Create('Not enough privileges');
    end
  );

That applies to all entities published by the server. Of course, if you want to restrict the code to some entities, you can check the Args.Entity property to verify the class of object being deleted and perform actions accordingly.

Finally, another nice example for authorization and server-side events. Suppose that every entity in your application has a property named "Protected" which means only admin users can see those entities. You can use a code similar to the one above to refuse requests that try to modify, create or retrieve a protected entity if the requesting user is not admin.

But what about complex queries that return multiple entities? In this case you can use the OnEntityList event, which will provide you with the Aurelius criteria (TCriteria) that will be used to retrieve the entities. You can then modify such criteria depending on user permissions:

  Module.Events.OnEntityList.Subscribe(procedure(Args: TEntityListArgs)
    var 
      User: IUserIdentity;
      IsAdmin: Boolean;
    begin
      User := Args.Handler.Request.User;
      IsAdmin := (User <> nil) and User.Claims.Exists('isadmin') and User.Claims['isadmin'].AsBoolean;
      if not IsAdmin then
        Args.Criteria.Add(TLinq.EqualsTo('Protected', false));
    end
  );

The code above simply checks if the requesting user has elevated privileges. If it does not, then it adds an extra condition to the criteria (whatever the criteria is) which filters only the entities that are not protected. So non-admin users will not see the protected entities in the server response.


For more information about the subject, here is a summary of the links related to the topics covered in this post:
TMS Business Subscription Page
TMS XData Product Page
TMS Sparkle Product Page
TMS XData Online Documentation
TMS Sparkle Online Documentation
JSON Web Token Web Site
Wikipedia article about JSON Web Token
JSON Web Token Specification - RFC-7519
Delphi JOSE and JWT library



Wagner R. Landgraf


Bookmarks: 

This blog post has received 11 comments.


1. Friday, May 06, 2016 at 8:11:00 PM

Hi Wagner

WOW!!! Thank you very much. This is a great feature!! ;-)

Pederzolli Patrizia


2. Monday, May 09, 2016 at 10:17:55 PM

Thanks a lot.
This makes TMS Business Suite so much more worth - great.

Roland Kossow


3. Wednesday, May 25, 2016 at 3:51:22 PM

Tenho acompanhado a evolução do Sparkle e me impressiona a sintonia com as necessidades dos desenvolvedores. A pouco tempo, o release do sistema middleware já havia dado maturidade plena ao framework e agora somos surpreendidos com a adição deste mecanismo de autenticação. Parabéns a TMS!

Wagner, aproveito para fazer uma pergunta: O uso da biblioteca open source "Delphi JOSE" significa que o sparkle JWT depende das DLLs OpenSSL no servidor?

Geziel Machado


4. Thursday, June 16, 2016 at 9:54:16 AM

How do I add the JWT token when using an xdata client?

Maarten Hennekam


5. Thursday, June 16, 2016 at 2:41:08 PM

You can use the XDataClient.HttpClient.OnSendingRequest to set the Authorization header with the JWT token.

Wagner R. Landgraf


6. Thursday, June 16, 2016 at 2:42:01 PM

Geziel, yes and no. For Delphi XE8 and earlier, yes, OpenSSL is needed IF you use JWT. For Delphi Seattle and up, it''s not needed.

Wagner R. Landgraf


7. Thursday, October 05, 2017 at 10:41:26 AM

How do I add JWT Token authentication in Api created using Remobjects SDK written in delphi xe-2

Aswathi


8. Thursday, October 05, 2017 at 1:07:10 PM

Hello, you mean sending the JWT from the client? Take a look at this topic of documentation, the last section named "Authentication using TXDataClient": http://www.tmssoftware.biz/business/xdata/doc/web/authentication_example_using_j.html

Wagner R. Landgraf


9. Monday, October 09, 2017 at 11:37:42 AM

Thanks.Actually i mean Server side authentication using JWT.

Aswathi


10. Monday, October 09, 2017 at 1:34:20 PM

Well, server side authentication using JWT is fully described in this post. Server is implemented using TMS XData. If you want to implement your server using RemObjects SDK than I''d suggest you ask RemObjects authors about that I don''t have experience with it.

Wagner R. Landgraf


11. Thursday, November 16, 2017 at 7:33:30 PM

Hello, I''m pretty new to web development and I was wondering how I would go about validating the username and password

David




Add a new comment:
Author:
Email:
  You will receive a confirmation mail with a link to validate your comment, so please use a valid email address.
Comment:
 
Change Image
Fill in the characters from the image above:
 

All fields are required.
 




Previous  |  Next  |  Index