Search Results for

    Show / Hide Table of Contents

    Authentication and Authorization

    TMS Sparkle provides easy-to-use, built-in authentication and authorization mechanisms. You can use several authentication mechanisms, like Basic or JWT, and implement an agnostic authorization server mechanism, independent of the authentication used.

    Here are the basic steps to implement it:

    1. Add one of more authentication middleware to your sparkle module. For example, Basic Authenticaction Middleware or JWT Authentication Middleware.

    2. Depending on the middleware used, upon authentication create the user identity and its claims.

    3. When implementing your server, authorize your requests, by checking for the User property in the request, verify if it exists (authenticated) and if it has the claims needed to perform the server request (authorization).

    The following topics explain the above steps in details and provide additional info.

    Adding Authentication Middleware

    To authenticate your incoming request, you need to add an authentication middleware to your TMS Sparkle module. The purpose of the authentication middleware is check for user credentials sent by the client, and then creating the user identity and claims based on those credentials. The user identity/claims will be added to the request, which will then be forwarded to the next request processor in the middleware pipeline. From that point, you will be able to use that info to authorize your requests.

    These are the available authentication middleware and a simple example about how to use them. For more info, go to the specific authentication middleware topic.

    JWT (JSON Web Token) Middleware (Delphi XE6 and up only)

    Authenticates your requests using JWT (JSON Web Token). This will look for a Bearer token in the request authentication header which contains the JWT. The user identity and claims will be automatically retrieved from the JWT itself. Basic usage is just create the middleware with the secret used to sign the token:

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

    The middleware just validates and extracts the token information. To proper create a full JWT authentication mechanism, your server has to somehow generate a token for the client (for example, upon a Login method). This is explained in more details in the Authentication Example using JWT (JSON Web Token).

    Basic Authentication Middleware

    Authenticates requests using Basic Authentication. It looks for the Basic keyword in the request authentication header and retrieves user name and password. It successful it pass user name and password to an event handler that should in turn create and return the proper user identity and claims. Here is an example:

    uses {...}, Sparkle.Middleware.BasicAuth, Sparkle.Security;
    
      Module.AddMiddleware(TBasicAuthMiddleware.Create(
        procedure(const UserName, Password: string; var User: IUserIdentity)
        begin
          // Implement custom logic to authenticate user based on UserName and Password
          // For example, go to the database, check credentials and then create an user
          // identity with all permissions (claims) for this user
          User := TUserIdentity.Create;
          User.Claims.AddOrSet('roles').AsString := SomeUserPermission;
          User.Claims.AddOrSet('sub').AsString := UserName;
        end,
        'My Server Realm'
      ));
    

    User Identity and Claims

    The authentication/authorization mechanism is based on user identity and claims. That is represented in Sparkle by the IUserIdentity interface, declared in unit User.Security:

    IUserIdentity = interface
      function Claims: TUserClaims;
    end;
    

    Such interface has a single function which return the user claims. Claims is a name/value mapping that holds information about the user, like its name, e-mail address, permissions, and any extra info you can add it. Each claim has a name and a value, and the value can be of several types, like integer, string, etc.

    The user identity is created and filled by the authentication middleware, and attached to the THttpRequest object in the User property.

    If you are implementing the authentication middleware processing, you might want to create a new user identity (class TUserIdentity is a ready-to-use class that implements IUserIdentity interface) and add claims to it using AddOrSet method, for example:

    uses {...}, Sparkle.Middleware.BasicAuth, Sparkle.Security;
    
      Module.AddMiddleware(TBasicAuthMiddleware.Create(
        procedure(const UserName, Password: string; var User: IUserIdentity)
        begin
          // Implement custom logic to authenticate user based on UserName and Password
          // For example, go to the database, check credentials and then create an user
          // identity with all permissions (claims) for this user
          User := TUserIdentity.Create;
          User.Claims.AddOrSet('roles').AsString := SomeUserPermission;
          User.Claims.AddOrSet('sub').AsString := UserName;
        end,
        'My Server Realm'
      ));
    

    If you are implementing your server logic and wants to check user identity and claims, you can use methods Exists or Find to get the user claim and then its value:

    var RolesClaim: TUserClaim;
    begin
      RolesClaim := nil;
      if Request.User <> nil then
        RolesClaim := Request.User.Find('roles');
      if RolesClaim <> nil then
        Permissions := RolesClaim.AsString;
    

    TUserClaims holds a list of TUserClaim objects which are destroyed when TUserClaims is destroyed. TUserClaims and TUserClaim classes provide several properties and methods for you to create and read claims.

    TUserClaims Methods

    Name Description
    function AddOrSet(Claim: TUserClaim): TUserClaim Adds a new claim to the user. If there is already a claim with the same name as the one being passed, it will be replaced by the new one. You don't need to destroy the TUserClaim object, it will be destroyed when TUserClaims is destroyed.
    function AddOrSet(const Name: string): TUserClaim Creates and adds a new claim with the specified name. If there is already a claim with same name, it will be destroyed and replaced by the new one. You don't need to destroy the new claim. Example:
    User.Claims.AddOrSet('isadmin').AsBoolean := True;
    function AddOrSet(const Name, Value: string): TUserClaim Creates and adds a new claim with the specified name and string value. If there is already a claim with same name, it will be destroyed and replaced by the new one. You don't need to destroy the new claim. Example:
    User.Claims.AddOrSet('email', 'user@myserver.com');
    function Exists(const Name: string): Boolean Returns true if a claim with specified name exists.
    function Find(const Name: string): TUserClaim Returns the TUserClaim object with the specified name. If it doesn't exist, returns nil.
    procedure Remove(const Name: string) Removes and destroys the claim with the specified name.
    property Items[const Name: string]: TUserClaim Returns a claim with the specified name. If the claim doesn't exist, an error is raised.
    for Claim in User.Claims You can use the for..in operator to enumerate all the claims in the user identity.

    TUserClaim Properties

    Name Description
    Name: string Contains the name of the claim.
    AsString: string Reads or writes the claim value as a string. If the value was not previously saved as a string, an error may be raised.
    AsInteger: Int64 Reads or writes the claim value as an Int64 value. If the value was not previously saved as an Int64 value, an error may be raised.
    AsDouble: Double Reads or writes the claim value as a double value. If the value is not a double value, an error may be raised.
    AsBoolean: Boolean Reads or writes the claim value as a boolean value. If the value was not previously saved as a boolean value, an error may be raised.
    AsEpoch: TDateTime Reads or writes the claim value as a date time value. When writing, saves it as a Unix time. When reading, it considers the existing value is a valid Unix time, and converts it to TDateTime. If the value is not a valid Unix time, an exception is raised.

    Authorizing Requests

    If you have property added an authentication middleware, the HTTP request you receive will already contain authentication information. Implementing your code is just as simple of examining the User property of the request, check if it exists and check the claims it contains.

    Suppose a very simple "hello world" module that refuses anonymous connections and only respond to requests from users which has a claim 'isadmin' set to true. It also considers that the authentication mechanism contains a claim named 'sub' which has the name of the user authenticated.

    uses {...}, Sparkle.Security;
    
    procedure TProtectedHelloModule.ProcessRequest(const C: THttpServerContext);
    var
      User: IUserIdentity;
      AdminClaim, NameClaim: TUserClaim;
      ResponseText: string;
    begin
      User := C.Request.User;
      if User = nil then 
        // not authenticated
        C.Response.StatusCode := 401 
      else
      begin
        AdminClaim := User.Claims.Find('isadmin');
        if not (Assigned(AdminClaim) and AdminClaim.AsBoolean) then
          // forbidden
          C.Response.StatusCode := 403; 
        else
        begin
          NameClaim := User.Claims.Find('sub');
          ResponseText := 'Hello';
          if NameClaim <> nil then
            ResponseText := ResponseText + ', ' + NameClaim.AsString;
    
    
          C.Response.StatusCode := 200;
          C.Response.ContentType := 'text/plain';
          C.Response.Close(TEncoding.UTF8.GetBytes(ResponseText));
        end;
      end;
    end;
    

    A more detailed example in Authentication Example using JWT (JSON Web Token).

    Creating JSON Web Tokens

    For JWT handling, Sparkle users a slightly modified version of the nice open source Delphi JOSE and JWT Library: http://github.com/paolo-rossi/delphi-jose-jwt.

    You can refer to that library page and documentation to learn how to create the tokens, for example to implement a login mechanism. The library provided in the Sparkle is mostly the same, with the main different that all units names are prefixed with "Bcl." to avoid unit name conflict. So for example, the unit JSON.Core.Builder becomes Bcl.Json.Core.Builder.

    Just for a reference, here is an example about how to generate a JWT:

    function TTestService.Login(const UserName, Password: string): string;
    var
      JWT: TJWT;
      PermissionsFromDatabase: string;
    begin
      // check if UserName and Password are valid, and retrieve User data from database
      // for example, PermissionsFromDatabase, and set desired claims accordingly
      JWT := TJWT.Create(TJWTClaims);
      try
        JWT.Claims.SetClaimOfType<string>('roles', PermissionsFromDatabase);
        JWT.Claims.SetClaimOfType<string>('roles', UserName);
        JWT.Claims.Issuer := 'XData Server';
    
        JWT.Claims.Expiration := IncHour(Now, 1); // Expire token one hour from now
        Result := TJOSE.SHA256CompactToken('my JWT secret', JWT);
      finally
        JWT.Free;
      end;
    end;
    
    In This Article
    Back to top TMS Sparkle v3.32
    © 2002 - 2025 tmssoftware.com