Show/Hide Toolbars

TMS XData Documentation

Navigation: Server-Side Events

Authentication Example using JSON Web Token (JWT)

Scroll Prev Top Next More

Authentication and Authorization mechanisms in XData are available through the built-in auth mechanisms provided in TMS Sparkle, the underlying HTTP framework which XData is based on. The authentication/authorization in Sparkle is generic and can be used for any types of HTTP server, not only XData. But this topic illustrates how to use it integrated with XData server-side events to build an authentication system 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 if we are just learning about it. There is plenty of information out there, 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 will 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 the header, the payload, and a secret that only the generator of the JWT knows.

 

The payload is the JSON object that "matters", it's the actualy content that end-user applications will read to perform actions. The header contains meta information the token, mostly the hashing algorithm using 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, generate 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 process actions accordingly.

 

Since only the server has the secret, there is no way the client can change the payload, adding "false" information to it for example. 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, 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 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, it's worth noting 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<Boolean>('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 modifi):

 

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 server will use to validate the signature of the tokens it will receive:

 

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 or not. The only situation where the middleware will return immediately is if the token is present and is invalid. In this case it will return an to the client immediately and your server code will not be executed.

 

Authorizing the requests

 

Finally, in your application specific 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 does an arbitrary action. You don't want to allow anonymous requests (not authenticated) to perform such action. And you will only execute such action is authenticated user is an adminstrator. 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. 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? 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:

 

  Module.Events.OnEntityList.Subscribe(procedure(Args: TEntityListArgs)
    var 
      User: IUserIdentity;
      IsAdmin: Boolean;
    begin
      User := Args.Handler.Request.User;
      IsAdmin := (User <> niland 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.

 

Authentication using TXDataClient

 

If you are using TXDataClient from a Delphi application to access the XData server, you can simply use the OnSendingRequest event to add authentication credentials (the token you retrieved from the server):

 

var
  Login: ILoginService;
  vToken: string;
begin
   vToken := Client.Service<ILoginService>.Login(edtUser.Text, edtPassword.Text);
   Client.HttpClient.OnSendingRequest := procedure(Req: THttpRequest)
     begin
       Req.Headers.SetValue('Authorization','Bearer '+vToken);
     end;
end;