Search Results for

    Show / Hide Table of Contents

    Authentication and Authorization

    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 mechanism in Sparkle is generic and can be used for any types of HTTP server, not only XData. But this topic illustrates how to better use specific XData features like server-side events and attributed-based authorization to make it even easier to secure your REST API.

    In this guide we will present the concept of JSON Web Token, then how to authenticate requests (make sure there is a "user" doing requests) and finally how to authorize requests (make sure such user has permissions to perform specific operations).

    Note

    Even though we are using JWT as an example, the authentication/authorization mechanism is generic. You can use other type of token/authentication mechanism, and the authorization mechanism you use will be exactly the same regardless what token type you use. Holger Flick's book TMS Hands-on With Delphi shows a good example of authentication/authorization using a different approach than JWT.

    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 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.

    Authentication

    Enough of theory, now we will show you how to do authentication 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 and attributes 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;
    

    Clients 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.

    Warning

    It's worth noting that in production code 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 User, Password: string): string;
    var
      JWT: TJWT;
      Scopes: string;
    begin
      { check if UserName and Password are valid, retrieve User data from database, 
        add relevant claims to JWT and return it. In this simplified example,
        we are doing a simple check for password }
      if User <> Password then
        raise EXDataHttpUnauthorized.Create('Invalid password');
      JWT := TJWT.Create;
      try
        JWT.Claims.SetClaimOfType<string>('user', User);
        if User = 'admin' then
          JWT.Claims.SetClaimOfType<Boolean>('admin', True);
        Scopes := 'reader';
        if (User = 'admin') or (User = 'writer') then
          Scopes := Scopes + ' writer';
        JWT.Claims.SetClaimOfType<string>('scope', Scopes);
        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": "writer",
      "Password": "writer"
    }
    

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

    HTTP/1.1 200 OK
    Content-Type: application/json
    
    {
        "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoid3JpdGVyIiwic2NvcGUiOiJyZWFkZXIgd3JpdGVyIiwiaXNzIjoiWERhdGEgU2VydmVyIn0.QdRTt6gOl3tb1Zg0aAJ74bepQwqm0Rd735FKToPEyFY"
    }
    

    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 /artist?$orderby=Name HTTP/1.1
    content-type: application/json
    authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoid3JpdGVyIiwic2NvcGUiOiJyZWFkZXIgd3JpdGVyIiwiaXNzIjoiWERhdGEgU2VydmVyIn0.QdRTt6gOl3tb1Zg0aAJ74bepQwqm0Rd735FKToPEyFY
    
    Note

    This authentication mechanism is a suggestion and is totally independent from the rest of this guide. The JWT token could be provided in any other way: a different service, different server, different sign-in mechanism (not necessarily username/password), or even a 3rd party token provider.

    Implementing JWT Authentication with TJwtMiddleware

    The second step is to add a JWT middleware to your server.

    At design-time, right-click the TXDataServer component, choose the option to manage the middleware list, and add a JWT Middleware to it. The middleware has an OnGetSecret event that you need to handle to pass to it the JWT secret our server will use to validate the signature of the tokens it will receive.

    If you are using the XData server module, all you need to do is to add a TJwtMiddleware and inform the secret in the constructor:

    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.

    Warning

    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. It's up to you to check if user is present in the request or not. If you want the token to prevent non-authenticated requests to be processed, set its ForbidAnonymousAccess to true.

    If the token is present and is invalid, it will return an error to the client immediately and your server code will not be executed.

    Using public/private (asymmetric) keys

    With XData you can also sign JWT usig private keys and validate JWT signed with public keys. This is actually a more secure and flexible approach compared to using a shared secret key.

    For example, if for some reason you don't want (or can't) share the secret with the APIs which are supposed to validate your JWT, then using asymmetric keys can be your choice. You will sign the JWT with the private key, which never leaves your server and doesn't need to be shared with anyone. You can distribuite the public key, which can be freely available, for the APIs to use it to validate JWT tokens you signed using the respective private key.

    Generating the keys

    Generating the keys is out of the scope of XData, you can use any cryptography tool for that, like OpenSSL for example. You just need to be aware that XData/Sparkle expects the keys to be in PEM text format. One convenient way to generate keys for Windows users is to use the builtin ssh-keygen tool:

    ssh-keygen -t rsa -b 2048 -m PEM -N "" -f rsa256-private.key
    ssh-keygen -e -m PEM -f rsa256-private.key > rsa256-public.key
    

    The above commands will create a file named rsa256-private.key containing the private key in PEM text format, and a file named rsa256-public.key contaning the public key in PEM text format. Another file named rsa256-private.key.pub will also be created but we will not used it, because it contains the public key in a format that is not supported by XData.

    Signing the JWT with private key

    You can simply use PEM private key as the "secret" to sign the JWT. You also need to inform the algorithm to sign. While the shared secret algorithm is HS256, the one we use here is RS256, because that's what we used to generate the keys above. The following code shows a generic code that can sign a JWT with either algorithm:

    const
      // Use your own secret in your applicaction
      JWTSecret = 'super_secret_0123456789_0123456789';
    
      // Set SignWithRSA to True to sig JWT usig asymmetric keys. In this case the JWT will be signed with a private key
      // ad later at the API will validated with the public key
      SignWithRSA = False;
    
      // Change KeyId if you recreate the private key
      RSAKeyId = 'D00CD046-FEDA-4120-9258-391371649A32';
    
    function TLoginService.Login(const User, Password: string): string;
    var
      JWT: TJWT;
      JWK: TJWK;
      SigningKey: TArray<Byte>;
      SigningAlgorithm: TJOSEAlgorithmId;
      Scopes: string;
    begin
      // Check if user name and password are correct. This is your app business logic
      // Here, we will accept anything as long password is the same as user name.
      // Of course, don't use this logic in your app
      if User <> Password then
        raise EXDataHttpUnauthorized.Create('Invalid password');
    
      JWT := TJWT.Create;
      try
        // Add custom information (claims) to the JWT if needed
        JWT.Claims.SetClaimOfType<string>('custom', 'data');
    
        // Now sign the JWT with the proper algorithm, either shared secret (HS256) or private key (RSA256)
        if SignWithRSA then
        begin
          // load private key from file
          SigningKey := TFile.ReadAllBytes('rsa256-private.key');
          SigningAlgorithm := TJOSEAlgorithmId.RS256;
          Jwt.Header.KeyID := RSAKeyId;
        end
        else
        begin
          SigningKey := TEncoding.UTF8.GetBytes(JWTSecret);
          SigningAlgorithm := TJOSEAlgorithmId.HS256;
        end;
    
        JWK := TJWK.Create(SigningKey);
        try
          Result := TJOSE.SerializeCompact(JWK, SigningAlgorithm, JWT, False);
        finally
          JWK.Free;
        end;
      finally
        JWT.Free;
      end;
    end;
    

    Validating the JWT with a public key

    You can use JWT Middleware OnGetSecretEx event to validate a JWT signature. From that event you can check the algorithm used to sign the JWT, the id of the key used to sign it, and then validate it properly.

    The following example shows how to validate a JWT signed with either HS256 algorithm (shared secret) or RS256 (asymmetric keys). If you don't want to support one of them, just remove the respective code.

    procedure TServerModule.XDataServer1JWTGetSecretEx(Sender: TObject; const JWT: TJWT; Context: THttpServerContext;
      var Secret: TBytes);
    begin
      if JWT.Header.Algorithm = 'HS256' then
        Secret := TEncoding.UTF8.GetBytes(JWTSecret)
      else
      if JWT.Header.Algorithm = 'RS256' then
      begin
        if JWT.Header.KeyID = RSAKeyId then
          Secret := TFile.ReadAllBytes('rsa256-public.key')
        else
          raise EJOSEException.CreateFmt('Unknown KeyId in JWT', [JWT.Header.KeyID]);
      end
      else
        raise EJOSEException.CreateFmt('JWS algorithm [%s] is not supported', [JWT.Header.Algorithm]);
    end;
    

    Authorization

    Now that we know how to check for authenticated requests, it's time to authorize the requests - in other words, check if the authenticated client/user has permission to perform specific operations.

    Attribute-based Authorization

    The easiest way to authorize your API is to simply add authorization attributes to parts of the code you want to specify permissions.

    Remember that a XData server has two mechanism for publishing endpoints: service operations and automatic CRUD endpoints. Each of them has different ways to be authorized.

    You must use unit XData.Security.Attributes to use authorization attributes.

    Authorize Attribute

    This attribute is can be used in service operations. Just add an Authorize attribute to any method in your service contract interface, and such method will only be invoked if the request is authenticated.

      [ServiceContract]
      IMyService = interface(IInvokable)
      ['{80A69E6E-CA89-41B5-A854-DFC412503FEA}']
    
        function NonRestricted: string;
    
        [Authorize]
        function Restricted: string;
      end;
    

    In the example above, the endpoint represented by the method NonRestricted will be publicly accessible, while the method Restricted will only be invoked if the request is authenticated. Otherwise, a 403 Forbidden response will be returned.

    You can also apply the Authorize attribute at the interface level:

      [ServiceContract]
      [Authorize]
      IMyService = interface(IInvokable)
      ['{80A69E6E-CA89-41B5-A854-DFC412503FEA}']
    
        function Restricted: string;
        function AlsoRestricted: string;
      end;
    

    In this case, the attribute rule will be applied to all methods of the interface. In the example above, both Restricted and AlsoRestricted methods will only be invoked if request is authenticated.

    AuthorizeScopes Attribute

    You can use AuthorizeScope attribute in service operations if you want to allow them to be invoked only if the specified scopes are present in user claims.

    It will check for a user claim with name scope, and check its values. The scope values in scope claim must be separated by spaces. For example, the scope claim might contain editor or reader writer. In the last example, it has two scopes: reader and writer.

        [AuthorizeScopes('admin')]
        procedure ResetAll;
    

    In the example above, the ResetAll method can only be invoked if the admin scope is present in the scope claim.

    You can specify optional scopes that will allow a method to be invoked, by separating scopes with comma:

        [AuthorizeScopes('admin,writer')]
        procedure ModifyEverything;
    

    In the previous example, ModifyEverything can be invoked if the scope claim contain either admin scope, or writer scope.

    You can add multiple AuthorizeScopes attributes, which will end up as two requirements that must be met to allow method to be invoked:

        [AuthorizeScopes('publisher')]
        [AuthorizeScopes('editor')]
        procedure PublishAndModify;
    

    For method PublishAndModify to be invoked, both scopes publisher and editor must be present in the scope claim.

    Note

    Just like Authorize attribute and any other authorization attribute, you can apply AuthorizeScopes attribute at both method and interface level. If you apply to both, then all requirements set by the authorization attributes added to interface and method will have to be met for the method to be invoked.

    AuthorizeClaims Attribute

    If you want to check for an arbitrary user claim, the use AuthorizeClaims attribute:

        [AuthorizeClaims('admin')]
        procedure OnlyForAdmins;
    

    The OnlyForAdmins method will only be executed if the claim 'admin' is present in user claims.

    You can also check for a specific claim value, for example:

        [AuthorizeClaims('user', 'john')]
        procedure MethodForJohn;
    

    In this case, MethodForJohn will only be executed if claim user is present and its value is john.

    As already described above, AuthorizeClaims attribute can be used multiple times and can be applied at both method and interface level.

    EntityAuthorize Attribute

    You can also protect automatically created CRUD endpoints using authorization attributes. Since those endpoints are based on existing Aurelius entities, you should apply those attributes to the entity class itself.

    Note

    Attributes for automatic CRUD endpoints are analogous to the ones you use in service operations. The different is they have a prefix Entity in the name, and receive an extra parameter of type TEntitySetPermissions indicating to which CRUD operations the attribute applies to.

    The simplest one is the EntityAuthorize attribute:

      [Entity, Automapping]
      [EntityAuthorize(EntitySetPermissionsWrite)]
      TCustomer = class
    

    In the previous example, to invoke endpoints that modify the TCustomer entity (like POST, PUT, DELETE), the request must be authenticated.

    Warning

    The rules are applied by entity set permission. In the previous example, the read permissions (GET a list of customer or a specific customer) are not specified, and thus they are not protected. User don't need to be authenticated to execute them.

    EntityAuthorizeScopes Attribute

    Similarly to AuthorizeScopes, you can restrict access to CRUD endpoints depending on the existing scopes using EntityAuthorizeScopes:

      [Entity, Automapping]
      [EntityAuthorizeScopes('reader', EntitySetPermissionsRead)]
      [EntityAuthorizeScopes('writer', EntitySetPermissionsWrite)]
      TArtist = class
    

    In the previous example, read operations will be allowed if and only if the scope reader is present. At the same time, the writer scope must be present to perform write operations.

    That means that to perform both read and write operations, the scope claim must have both reader and writer values: reader writer.

    One alternative approach is the following:

      [Entity, Automapping]
      [EntityAuthorizeScopes('reader,writer', EntitySetPermissionsRead)]
      [EntityAuthorizeScopes('writer', EntitySetPermissionsWrite)]
      TArtist = class
    

    In the case above, if the scope just have writer, then it will be able to perform both read and write operations, since read permissions are allowed if either reader or writer are present in scope claim. Either approach is fine, it's up to you to decide what's best for your application.

    EntityAuthorizeClaims Attribute

    Finally, similarly to AuthorizeClaims attribute, you can use EntityAuthorizeClaims to allow certain operations only if a claim exists and/or has a specific value:

      [Entity, Automapping]
      [EntityAuthorizeClaims('user', 'john', [TEntitySetPermissions.Delete]])]
      TArtist = class
    

    In the example above, only users with claim user equals john will be able to delete artists.

    Manual Authorization in Service Operations

    Finally, in addition to authorization attributes, you can always add specific code that authorizes (or forbids) specific operations based on user identity and claims. All you have to do is check for the request 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 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('admin') and User.Claims['admin'].AsBoolean) then
        raise EXDataHttpForbidden.Create('Not enough privileges');
    
      // if code reachs here, user is authenticated and is administrator
      // execute the action
    end;
    

    Using Server-Side Events

    You can also use server-side events to protect the entity sets published by XData, and add custom code to it. For example, you can use the OnEntityDeleting event to forbid non-admin users from deleting 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('admin') 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 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 <> nil) and User.Claims.Exists('admin');
        if not IsAdmin then
          Args.Criteria.Add(not Linq['Protected']));
      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.

    Using Authentication Credentials with 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;
      JwtToken: string;
    begin
       JwtToken := Client.Service<ILoginService>.Login(edtUser.Text, edtPassword.Text);
       Client.HttpClient.OnSendingRequest := procedure(Req: THttpRequest)
         begin
           Req.Headers.SetValue('Authorization','Bearer ' + JwtToken);
         end;
    end;
    
    In This Article
    Back to top TMS XData v5.21
    © 2002 - 2025 tmssoftware.com