Blog
All Blog Posts | Next Post | Previous PostRest Server Authentication using JWT (JSON Web Token)
Friday, May 6, 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 Landgraf
This blog post has received 11 comments.
This makes TMS Business Suite so much more worth - great.
Roland Kossow
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
Maarten Hennekam
Wagner R. Landgraf
Wagner R. Landgraf
Aswathi
Wagner R. Landgraf
Aswathi
Wagner R. Landgraf
David
All Blog Posts | Next Post | Previous Post
WOW!!! Thank you very much. This is a great feature!! ;-)
Pederzolli Patrizia