JWT authentication with Delphi by Paolo Rossi

emailx45

Местный
Регистрация
5 Май 2008
Сообщения
3,571
Реакции
2,438
Credits
573
JWT authentication with Delphi
Paolo Rossi -- April 27, 2017
[SHOWTOGROUPS=4,20]
JWT authentication with Delphi. Part 1

JWT Authentication with Delphi Series
  1. Part 1: Authorization and JWT basic concepts
  2. Part 2: The JWT in depth
  3. Part 3: Building and verifying JWTs in Delphi
  4. Part 4: Using the Consumer to validate the JWT
This is the first article I will write about JWT and authentication technologies using Delphi, specifically I'll cover the topic of authentication (mostly in a HTTP world) using tokens (specifically JSON Web Tokens).

The Delphi library used in this article is the open source Для просмотра ссылки Войди или Зарегистрируйся library (created by me) and available on Для просмотра ссылки Войди или Зарегистрируйся. This library is listed on the Для просмотра ссылки Войди или Зарегистрируйся site and it's already used in several projects (open source and commercial) but before we dive into the code let’s cover some basics about JWT's.

Authentication
Authentication is the process of identifying someone (or something) determining that is who is claimed to be. Remember that in this article I will be speaking only about authentication and not authorization (that is giving individuals access to system objects based on their identity)
Для просмотра ссылки Войди или ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся
Session based authentication
In the HTTP world, applications have traditionally used session cookies to store authentication information. The mechanism relies on session IDs stored server-side. The session storage is typically an in-memory list/table (that is server-specific), or a separate session storage layer (often the back-end database).
In HTTP the communication is essentially stateless, so the cookie (that contains the session ID) is used by the server as a “key” to retrieve information about the client side (user or process). The cookie is created by the server and sent to the client, in the next request, the cookie is bounced back so the server can lookup the session ID in the session table.
Disadvantages of “cookie” based auth:
  • Sessions: Sessions are just stored on server’s memory
  • Mobile: Native mobile apps seems to have problems working with cookies so if we need to query a remote API, maybe session auth is not the best solution.
  • CSRF: (Cross-Site Request Forgery) If we go down the cookies way, you really need to do CSRF to avoid cross site requests. That is something we can forget when using JWT as you will see.
  • CORS: (Cross-Origin Resource Sharing) Have you fight with CORS and cookies? No need to wrestle using JWT.
Token based authentication
The main problem of a session based authentication is that the server must maintains a list of session to be able to “validate” the incoming request and that is a problem because only one server knows how to validate a client request (no scalability or availability).

So, token authentication was developed to solve problems of server-side session IDs. Using tokens instead of session IDs has the effect of lowering the server load and remove the need of storing an in-memory session table or having an expensive session storage layer (performance). JWT are stateless by definition so they are perfect for this task (more on that, later).

Before a token is created the user must, obviously, supply verifiable credentials (standard username/password pair, API keys, hardware IDs, or even tokens from another service) and consequently perform some sort of “login” action.

Keep in mind that JWT is not the only “standard” token representation out there, SWT (Simple Web Token) is (was) a proposed standard (Microsoft 2009) and SAML (Security Assertion Markup Language Token) is an open-standard for exchanging authentication and authorization data between parties based on XML (SAML 2.0, OASIS Standard 2005).

OAuth 2
Often I read about OAuth 2 and JWT as if they were comparable (and competing) standards… well, they are not!

OAuth 2 is an authorization framework that can employ JWT as the format for the OAuth 2 tokens, remember that OAuth2 is not an authentication protocol (because OAuth2 doesn’t know nothing about the user). OAuth 2 is a rather complex topic and I think I will write another article on this topic.

JSON Web Token (JWT)
A JSON Web Token or JWT (pronounced “jot”) is a signed piece of data in JSON format and because it's signed the recipient (the server) can, and must, verify its authenticity.

The workflow is basically this: a user wants to authenticate so he sends the username and password (for example), the server validates the user and creates a (cryptographically) signed token and then sends it back to the user. The user sends the token with the next request and the server checks if the signature is genuine and (eventually) grants the access to the requested resource. In detail:
  1. The user sends username and password to an authentication service
  2. The authentication service responds with a signed JWT with information about the user
  3. The user requests a resource on the server sending the token back
  4. The server checks the signature and if it's genuine the access is granted
Для просмотра ссылки Войди или ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся
A first look to a JWT
This is what a JWT looks like:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

The same JWT decoded:
Код:
{
"alg": "HS256",
"typ": "JWT"
}
.
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
.
HMACSHA256(
base64UrlEncode(header),
base64UrlEncode(payload),
secret
)

As you can see the token is composed of three parts:
  1. In the first part (called header) are stored information about the signing algorithm and the type of payload (JWT)
  2. The second (the payload) contains the actual user data (claims)
  3. The third part is the signature computed (in this example) with the HMACSHA256 algorithm.
What is JOSE?
Before I talked about the Delphi JWT library Для просмотра ссылки Войди или Зарегистрируйся, but what is JOSE? (and no, is not my name!). JOSE stands for JSON Object Signing and Encryption and is a (set of) standard that provides a general approach to signing and encryption of any content. JOSE consists of several RFC:
In a nutshell: the JWT contains the claims, the JWS is the JWT when signed, the JWE is the JWT when encrypted, the JWA defines the algorithms used in JOSE and the JWK describes the handling of the cryptographic keys used in the process.

Often the term JWT is used when describing some other JOSE definitions so, for simplicity, in this article I will be using the term JWT.

JWT and REST
JWT have become very popular with the wide adoption of REST architectural style, but we can use JWT tokens to authenticate in various context, not only REST applications.

Tokens (and JWTs) are merely an authentication representations and so they can be used in multiple scenarios:
  • REST services authentication
  • OAuth 2.0 communications
  • CSRF (Cross Site Request Forgery) protection schemes
  • More in general as session IDs (eventually inside a cookie)
In this article(s) I will focus on REST technologies but I will give some example of using JWT in other contexts.

Для просмотра ссылки Войди или ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся
JWT benefits in a RESTful service
In his famous dissertation, Roy T. Fielding defines 6 constraints for (truly) RESTful services:
  1. Client/server architecture
  2. Stateless communication
  3. Cache (on the client)
  4. Uniform Interface
  5. Layered system
  6. Code on-demand
The most important (to me) is the second (and consequently the third one) that states REST interactions between client and server must be stateless by nature.

That means that requests (from the client) must contain all of the information necessary to understand the request and so they cannot take advantage of any stored context on the server and this, unfortunately, includes the session table typically stored on servers (automatic session management is one of the most publicized features of HTTP server frameworks).

The second constraint (if satisfied) induces the properties of visibility (looking at a request is sufficient to visualize the interaction), reliability (failure of one request does not influence others), and scalability (a server can switch a request to another server). Given these advantages you can see why this constraint is so important when building a REST server! Oh, and remember that JWT helps you to achieve this goal because:
  • Using JWTs there’s no need of sessions
  • Using JWTs there’s no need of session storage (on server)
  • Using JWTs there’s no need of garbage collection of expired sessions
Conclusion
So, as you can see, JWT is a simple and yet powerful technology to accomplish several tasks.

In the next post I will explain in detail the JWT’s claims and we'll start to explore the Для просмотра ссылки Войди или Зарегистрируйся library features.

[/SHOWTOGROUPS]
 

emailx45

Местный
Регистрация
5 Май 2008
Сообщения
3,571
Реакции
2,438
Credits
573
[SHOWTOGROUPS=4,20]
In the first part of this article, I introduced the concept of authentication, the benefits using token-based authentication (opposed to session-based authentication), the use of JWT in a REST service, and we had a first look at the JWT, now it's the time to dig deep in the understanding of the JSON Web Token.

The JWT in depth
Let’s take another look at this example of JWT:

Код:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Header
Код:
{
  "alg": "HS256",
  "typ": "JWT"
}

The JOSE header has slightly different set of registered parameters depending on the fact that it’s a JWS or a JWE header, below you can find the list of the registered names of params:

  • alg (Algorithm) identifies the cryptographic algorithm used to secure the JWS/JWE
  • typ (Type) is used by JWS applications to declare the media type of this complete JWS/JWE
  • kid (Key ID) is a hint indicating which key was used to secure the JWS/JWE
  • cty (Content Type) used by JWS/JWE applications to declare the media type of the secured content (aka the payload)
  • jku (JWK Set URL) is a URI that refers to a resource for a set of JSON-encoded public keys, one of which corresponds to the key used to digitally sign the JWS (JWE)
  • jwk (JSON Web Key) is the public key that corresponds to the key used to digitally sign the JWS/JWE
  • x5u (X.509 URL) is a URI that refers to a resource for the X.509 public key certificate or certificate chain corresponding to the key used to digitally sign the JWS/JWE
  • x5c (X.509 certificate chain) contains the X.509 public key certificate or certificate chain corresponding to the key used to digitally sign the JWS/JWE
  • x5t (X.509 certificate SHA-1 thumbprint) is a base64url-encoded SHA-1 thumbprint (aka digest) of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign the JWS/JWE
  • x5t\#S256 (X.509 certificate SHA-256 thumbprint) is a base64url-encoded SHA-256 thumbprint (aka digest) of the DER encoding of the X.509 certificate [RFC5280] corresponding to the key used to digitally sign the JWS/JWE
  • crit (Critical) indicates that extensions tothis specification and/or [JWA] are being used that MUST be understood and processed
The JWE standard defines two more header

  • enc (Encryption Algorithm) identifies the content encryption algorithm used to perform authenticated encryption on the plaintext to produce the ciphertext and the Authentication Tag
  • zip (Compression Algorithm) defines the zip (compression algorithm) applied to the plaintext before encryption, if any
Typically you will see JWT tokens with only the first two parameters.

alg (Algorithm)
The value of the alg parameter can be anything specified in the JSON Web Algorithms specification (JWA). Here's the registered algorithm list:
  • HS256 - HMAC using SHA-256
  • HS384 - HMAC using SHA-384
  • HS512 - HMAC using SHA-512
  • RS256 - RSASSA-PKCS1-v1_5 using SHA-256
  • RS384 - RSASSA-PKCS1-v1_5 using SHA-384
  • RS512 - RSASSA-PKCS1-v1_5 using SHA-512
  • ES256 - ECDSA using P-256 and SHA-256
  • ES384 - ECDSA using P-384 and SHA-384
  • ES512 - ECDSA using P-521 and SHA-512
  • PS256 - RSASSA-PSS using SHA-256 and MGF1 with SHA-256
  • PS384 - RSASSA-PSS using SHA-384 and MGF1 with SHA-384
  • PS512 - RSASSA-PSS using SHA-512 and MGF1 with SHA-512
  • none - No digital signature or MAC performed *

Please note the last one, none, which is the most interesting from the security perspective. It's been known to be used for a downgrade algorithm attack: imagine a JWT is generated by the client with some made up claims. It specifies the none signature algorithm in the header and it sends it for verification. The issuer takes the alg parameter as true and grants access where it shouldn't. The security layer of your app should always be suspicious about the alg parameter from the header.

* The alg header could be a source of security problems if not handled with care and if you don’t use a library aware of this vulnerability. (The Для просмотра ссылки Войди или Зарегистрируйся is safe in the context of the alg: none vulnerability)

More on the JWT attacks and vulnerabilities in the next part.

typ (Type)
Is defined by JWS and JWE and is used by JWT applications to declare the media type of this complete JWT. The application can use this value to disambiguate among the different kinds of objects that might be present. If specified, it should be spelled in capitals: "JWT".

kid (Key ID)
If the security layer in your app uses just one algorithm for signing the JWT, you don't have to use the alg parameter, because you are always checking integrity of the token against the same key and algorithm. If however, your app uses different algorithms and keys, you need to be able to figure out which the token was signed with.
As we saw earlier, relying on the alg parameter alone may lead to some security problems. Using the kid, you can specify a unique string for each algorithm used so when receiving the JWT you can rely on more information about the algorithm used.

cty (Content Type)
This header parameter is defined by the JWS and JWE specs and is used to to declare the media type (IANA MediaTypes) of the secured content (the payload). This is intended for use by the application when more than one kind of object could be present in the JWS Payload, otherwise the cty parameter must not be used.

Для просмотра ссылки Войди или ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся
Payload
The payload contains the actual data and, like the header, is a JSON object.
Код:
{
  "jti": "wirl-20170423-1234297765",
  "sub": "paolo",
  "iss": "paolorossi.net",
  "exp": 1300917320,
  "nbf": 1300915320,
  "admin": true
}

Claims
Claims are assertions made about a certain party. There are claims specified in the standard called registered claims (with specific meaning) and claims that the user can add to the JWT (public and private claims).

Registerd Claim Names
  • iss (Issuer). Identifies the principal that issued the JWT. The iss value is a case-sensitive string. Use of this claim is optional.
  • sub (Subject). Identifies the principal that is the subject of the JWT. The subject value must either be scoped to be locally unique in the context of the issuer or be globally unique. The sub value is a case-sensitive string. Use of this claim is optional.
  • aud (Audience). Identifies the recipients that the JWT is intended for. If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT must be rejected. In the general case, the aud value is an array of case-sensitive strings. In the special case when the JWT has one audience, the aud value may be a single case-sensitive string. Use of this claim is optional.
  • exp (Expiration Time). Identifies the expiration time on or after which the JWT must not be accepted for processing. The processing of the exp claim requires that the current date/time must be before the expiration date/time listed in the exp claim. Implementers may provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value must be a number containing a NumericDate value. Use of this claim is optional.
  • nbf (Not Before). Identifies the time before which the JWT must not be accepted for processing. The processing of the nbf claim requires that the current date/time must be after or equal to the not-before date/time listed in the nbf claim. Implementers may provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value must be a number containing a NumericDate value. Use of this claim is optional.
  • iat (Issued At). Identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value must be a number containing a NumericDate value. Use of this claim is optional.
  • jti (JWT ID). Provides a unique identifier for the JWT. The identifier value must be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions must be prevented among values produced by different issuers as well. The jti claim can be used to prevent the JWT from being replayed. The jti value is a case-sensitive string. Use of this claim is optional.
Public Claim Names
Claim Names can be defined but any new Claim Name should either be registered in the IANA "JSON Web Token Claims" registry or be a Public Name: a value that contains a Collision-Resistant Name. In each case, the definer of the name or value needs to take reasonable precautions to make sure they are in control of the part of the namespace they use to define the Claim Name.

Private Claim Names
A producer and consumer of a JWT may agree to use Claim Names that are Private Names: names that are not Registered Claim Names or Public Claim Names. Unlike Public Claim Names, Private Claim Names are subject to collision and should be used with caution. In the JWT example above the admin claim is a Private Claim Name.

Signature (JSON Web Signature)
JSON Web Signature is probably the single most useful feature of JWT. By combining a simple data format with a well-defined series of signature algorithms, JWT is quickly becoming the ideal format for safely sharing data between clients and servers.

The purpose of a signature is to allow one or more parties to verify the authenticity of the JWT (meaning the data contained in the JWT has not been tampered with). Please note that a signature does not prevent other parties from reading the payload inside the JWT. This is what encryption (JWE) have to be used. IMO the use of JWE is rarely required when the JWT is mainly used for authentication purposes.

Для просмотра ссылки Войди или ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся
Conclusion
In the next post I will start to explore (I swear!) the Для просмотра ссылки Войди или Зарегистрируйся library so we can see how to perform authentication in our Delphi applications.

[/SHOWTOGROUPS]
 
Последнее редактирование:

emailx45

Местный
Регистрация
5 Май 2008
Сообщения
3,571
Реакции
2,438
Credits
573
[SHOWTOGROUPS=4,20]
Now that we have introduced the JSON Web Token in Part 1 and dissected it in Part 2, we are ready to fire up Delphi and start writing some code to generate, verify, and validate some JWT tokens.

Cryptographic key considerations
Код:
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  "secret"
)

Nearly all JWT's examples (even mines) use the word 'secret' as the secret key to sign the token but this is problematic because it is too short for the HS256 algorithm (or HS384 or HS512) so it's quite ineffective, in fact this can be quite dangerous from a security perspective.

Speaking of cryptographic hash functions (HMACxxx) we can set up some general rules: given a hash function H with a block size B and a byte-length of hash output L we can say that (Для просмотра ссылки Войди или Зарегистрируйся):

The key for HMAC can be of any length (keys longer than B bytes are first hashed using H). However, less than L bytes is strongly discouraged as it would decrease the security strength of the function. Keys longer than L bytes are acceptable but the extra length would not significantly increase the function strength. (A longer key may be advisable if the randomness of the key is considered weak)
Bottom line: Please change your signing key accordingly to your security needs!

Для просмотра ссылки Войди или ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся

Enough! let me see the code!
jwt-demo-debugger.png


Building a signed JWT (JWS)
Using the Для просмотра ссылки Войди или Зарегистрируйся library, the creation of the JWT is basically a three-step process:

  1. The first step is the definition of the token's standard claims like Subject, Expiration, JWT ID, and Issuer
  2. The second step is the cryptographic signing of the JWT (JWS)
  3. The final step is the JWT conversion to a URL-safe string, according to the JOSE rules
As you well know, the resulting JWT will be a base64-encoded string divided in 3 parts and signed with the specified key and signature algorithm. After that, you can take the token and (for example) send it to a REST client.

This is a full example that shows the construction of a JWT using the proper JOSE objects. Keep in mind that this is the powerful but "complex" way to build the JWT token but don't worry, the library exposes the TJOSE static class that greatly simplify the construction of the JWT token (we'll see it later).

Код:
procedure TfrmSimple.btnBuildClick(Sender: TObject);
var
  LToken: TJWT;
  LSigner: TJWS;
  LKey: TJWK;
  LAlg: TJOSEAlgorithmId;
begin
  LToken := TJWT.Create;
  try
    LToken.Claims.Issuer := 'Delphi JOSE Library';
    LToken.Claims.IssuedAt := Now;
    LToken.Claims.Expiration := Now + 1;

    // Signing algorithm
    case cbbAlgorithm.ItemIndex of
      0: LAlg := TJOSEAlgorithmId.HS256;
      1: LAlg := TJOSEAlgorithmId.HS384;
      2: LAlg := TJOSEAlgorithmId.HS512;
    else LAlg := TJOSEAlgorithmId.HS256;
    end;

    LSigner := TJWS.Create(LToken);
    try
      LKey := TJWK.Create(edtSecret.Text);
      try
        // With this option you can have keys < algorithm length
        LSigner.SkipKeyValidation := True;

        LSigner.Sign(LKey, LAlg);

        memoJSON.Lines.Add('Header: ' + TJSONUtils.ToJSON(LToken.Header.JSON));
        memoJSON.Lines.Add('Claims: ' + TJSONUtils.ToJSON(LToken.Claims.JSON));

        memoCompact.Lines.Add('Header: ' + LSigner.Header);
        memoCompact.Lines.Add('Payload: ' + LSigner.Payload);
        memoCompact.Lines.Add('Signature: ' + LSigner.Signature);
        memoCompact.Lines.Add('Compact Token: ' + LSigner.CompactToken);
      finally
        LKey.Free;
      end;
    finally
      LSigner.Free;
    end;
  finally
    LToken.Free;
  end;
end;

You can see the whole project in the "sample" directory on Для просмотра ссылки Войди или Зарегистрируйся, now let's analyze in detail this code. The Delphi classes used to build a JWT token are:
Код:
var
  LToken: TJWT;
  LSigner: TJWS;
  LKey: TJWK;
  LAlg: TJOSEAlgorithmId;

The TJWT class represent the token (filled with the claims), the TJWK represent the key used to sign the token, the TJWS it's the class that does the signing, and the TJOSEAlgorithmId is the type (enum type) of the algorithm used, in short:

The TJWS signs the TJWT's claims using the TJWK key with the TJOSEAlgorithmId algorithm
Код:
  LToken := TJWT.Create;

  // Here you can fill all of your claims
  LToken.Claims.Issuer := 'Delphi JOSE Library';
  LToken.Claims.IssuedAt := Now;
  LToken.Claims.Expiration := Now + 1;

In order to have a signed token we must create the token and fill the appropriate claims (keep in mind that you can also use a derived class from the TJWTClaims base class)

Код:
  // Here you choose the signing algorithm
  case cbbAlgorithm.ItemIndex of
    0: LAlg := TJOSEAlgorithmId.HS256;
    1: LAlg := TJOSEAlgorithmId.HS384;
    2: LAlg := TJOSEAlgorithmId.HS512;
  else LAlg := TJOSEAlgorithmId.HS256;
  end;

With the code above we simply choose the algorithm used to create the digital signature of our token.

Код:
  // To actually sign the token we create a TJWS object
  LSigner := TJWS.Create(LToken);

  // Create the key object
  LKey := TJWK.Create(edtSecret.Text);

  // With this option you can have keys < algorithm length
  LSigner.SkipKeyValidation := True;

  // Sign the JWT with the chosen key and algorithm
  LSigner.Sign(LKey, LAlg);

And here is the code that does the actual signing and conversion into a compact representation of the JWT:

  1. You have to create a TJWK object (the key) and a TJWS object (the signer)
  2. Then you call the Sign() method to actually sign the JWT (with the key and algorithm as parameters)
The LSigner.SkipKeyValidation := True line is telling the TJWS class that it can skip the validation of the provided key. The validation routine validates against null and (too) short keys.

Код:
  // Header: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
  LSigner.Header

  // Payload: eyJpc3MiOiJEZWxwaGkgSk9TRSBMaWJyYXJ5IiwiaWF0IjoxNTM0Nzc5NzQxLCJleHAiOjE1MzQ4NjYxNDF9
  LSigner.Payload

  // Signature: kevr4S3GBixywzvlZ0L9ZRRJb6osJ5WAiEATu6fuAK8
  LSigner.Signature

  // Compact Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJEZWxwaGkgSk9TRSBMaWJyYXJ5IiwiaWF0IjoxNTM0Nzc5NzQxLCJleHAiOjE1MzQ4NjYxNDF9.kevr4S3GBixywzvlZ0L9ZRRJb6osJ5WAiEATu6fuAK8
  LSigner.CompactToken

After that the JWS is ready and you can access the signed token in a compact representation and send it (for example) to a REST client.

Now that we saw the "right" way to build a signed token (JWS) I can show you the TJOSE class that simplifies the process but, keep in mind, that if you need extra control over the creation of your token you can always use the native JOSE classes.

Код:
procedure TfrmSimple.BuildJWT;
var
  LToken: TJWT;
begin
  // Create a JWT Object
  LToken := TJWT.Create;
  try
    // Token claims
    LToken.Claims.Subject := 'Paolo Rossi';
    LToken.Claims.IssuedAt := Now;
    LToken.Claims.Expiration := Now + 1;
    LToken.Claims.Issuer := 'Delphi JOSE Library';

    // Signing and compact format creation.
    // Please use a different secret key in production!!!!!
    memoCompact.Lines.Add(TJOSE.SHA256CompactToken('mysecretkey', LToken));
  finally
    LToken.Free;
  end;
end;

As you can see after building your JWT with the needed claims, to get the signed token you only have to call TJOSE.SHA256CompactToken('secret', LToken). Pretty simple right?

Obviously there are other utility methods such as SHA384CompactToken() and SHA512CompactToken() to cover other key lengths. The only caveat here is that using the SHA* methods the SkipKeyValidation property of the TJWS is always set to True.


[/SHOWTOGROUPS]
 
Последнее редактирование:

emailx45

Местный
Регистрация
5 Май 2008
Сообщения
3,571
Реакции
2,438
Credits
573
[SHOWTOGROUPS=4,20]


Для просмотра ссылки Войди или ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся


Decode and verify tokens

The process of verifying a compact token is very simple using the Для просмотра ссылки Войди или Зарегистрируйся library but before showing the code I want to clarify some (possible) confusion about the token verification and validation.

Verification vs Validation
In order to ensure that the message (payload) wasn't changed along the way, we have to verify the signature of the token.

After a token (the signature) is verified we can validate its claims so, for example, we can check if the given token is expired or if the subject it's the expected subject, etc...

As you can see the most important thing to do when you receive a token is to verify it because if the signature has been forged or the payload has been tampered with you cannot trust the claims, therefore there is no point in validating them.

Token verification
The verification is a very straightforward process. As usual I will show you the complete snippet using the JOSE classes first:

Код:
procedure TfrmSimple.VerifyTokenComplete;
var
  LKey: TJWK;
  LToken: TJWT;
  LSigner: TJWS;
begin
  LKey := TJWK.Create(edtSecret.Text);
  try
    LToken := TJWT.Create;
    try
      LSigner := TJWS.Create(LToken);
      try
        LSigner.SetKey(LKey);
        LSigner.CompactToken := FCompact;

        if LSigner.VerifySignature then
          memoJSON.Lines.Add('Token signature is verified')
        else
          memoJSON.Lines.Add('Token signature is not verified')
      finally
        LSigner.Free;
      end;
    finally
      LToken.Free;
    end;
  finally
    LKey.Free;
  end;
end;

You have to create the TJWK, TJWT, TJWS objects, then you have to set the Key object and the compact token for the LSigner object: LSigner.SetKey(LKey) and LSigner.CompactToken := FCompact. Now you can call the VerifySignature() method and check if the given compact token is a valid token, only after that you can safely access to the token's claims.

Код:
procedure TfrmSimple.VerifyToken;
var
  LToken: TJWT;
begin
  // Unpack and verify the token
  LToken := TJOSE.Verify(edtSecret.Text, FCompact);

  if Assigned(LToken) then
  begin
    try
      if LToken.Verified then
        memoJSON.Lines.Add('Token signature verified, you can trust the claims')
      else
        memoJSON.Lines.Add('Token signature not verified')
    finally
      LToken.Free;
    end;
  end;
end;

To further simplify the verification task, you can use the TJOSE utility class. The TJOSE.Verify() method takes the key and the compact token representation and returns a TJWT object but, remember, that you have to check if the the TJWT itself is verified before accessing its claims.

Claims validation
The validation of the token's claims cannot be fully "automated" because it's the user choice (and responsibility) to set the claims and to check them.

The JWT standard Для просмотра ссылки Войди или Зарегистрируйся defines some "standard" claims as I explained in Для просмотра ссылки Войди или Зарегистрируйся. In a "real" situation some claims are very important, like the Expiration Time exp, the Subject sub or the Issued At iat, etc... So when you receive back the token, after the signature verification you must also validate the claims found in the token.

Код:
  if LToken.Verified then
  begin
    // Claims validation (for more see the JOSE.Consumer unit)

    if LToken.Claims.Subject <> 'Paolo Rossi' then
      memoJSON.Lines.Add('Subject [sub] claim value doesn''t match expected value');

    if LToken.Claims.Expiration > Now then
      memoJSON.Lines.Add('Expiration time passed: ' + DateTimeToStr(LToken.Claims.Expiration));
  end;

This is only an example of claims validation but you can, of course, add your own logic to the validation process. In addition you can also create JWTs with custom claims and therefore you have to do some custom validations.

That said, the Для просмотра ссылки Войди или Зарегистрируйся library contains some classes that greatly simplify the claims validation process. You can find the classes in the JOSE.Consumer.pas and JOSE.Consumer.Validators.pas units (more on this in the next article)

jwt-demo-consumer.png


Conclusion
In this part we have described how to build, decode, verify and validate a JWT token. In the next part I will explain in detail the use of custom claims in a JWT token (TJWTClaimsClass class reference) and the JOSE.Consumer.* classes to validate the standard claims that strictly follow the JWT standard.


[/SHOWTOGROUPS]
 
Последнее редактирование:

emailx45

Местный
Регистрация
5 Май 2008
Сообщения
3,571
Реакции
2,438
Credits
573
[SHOWTOGROUPS=4,20]
In the previous three articles I've covered the basics concerning the use of the JWT in Delphi (token generation, signature validation). In this, and in the next articles, I want to show some advanced concepts of using the JWT. A few topics I will cover:

  • Powerful claims validation with the JWT Consumer
  • Use of Custom Claims
  • Best Practices using JWT in REST Services
  • Security consideration (revisited)
JWT Consumer* classes
In a later update I introduced some classes (interfaces) that help the validation process of the JWT's claims. To be clear, in order to validate only the compact token format or the signature you don't need these *Consumer classes, you simply call the TJOSE class-method Verify(), here the signatures of the method:

Код:
    class function Verify(AKey: TJWK; const ACompactToken: TJOSEBytes; AClaimsClass: TJWTClaimsClass = nil): TJWT; overload;
    class function Verify(AKey: TJOSEBytes; const ACompactToken: TJOSEBytes; AClaimsClass: TJWTClaimsClass = nil): TJWT; overload;

In a real use case of the JWT token (such as a REST framework) validating the signature is a "sine qua non" condition but, usually, you would like to validate other aspect of the JWT: is it expired? is the subject correct? or perhaps you want to take into account some seconds to avoid a too strict time-based comparison.
For all these scenarios the TJOSEConsumer (and TJOSEConsumerBuilder) will come to your rescue!

Для просмотра ссылки Войди или ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся
JWT validation framework
The Delphi JOSE-JWT library provides a secure framework that takes care of all necessary steps to validate a JWT:

  1. Token parsing: The access token (compact token) is parsed as a JWT. If the parsing fails, then the token is considered invalid.
  2. Signature validation: The digital signature is automatically verified by the library recreating the signature with the payload, the algorithm and the secret kept on the server.
  3. Algorithm checking: The algorithm specified in the JWT header is checked against the expected algorithms, if it doesn't match, then it's considered invalid. This feature prevents downgrade attacks and other attacks against a JWT.
  4. Claims validation: The JWT claims are validated following the RFC's rules. The claims validation phase is fully customizable through anonymous functions.
A first look at the source code to properly validate a JWT token using a TJOSEConsumer:

Код:
procedure TfrmConsumer.actBuildJWTConsumerExecute(Sender: TObject); 
begin

  ProcessConsumer(TJOSEConsumerBuilder.NewConsumer

    // Set the appropriate claims class (if needed)
    .SetClaimsClass(TJWTClaims)

    // JWS-related validation
    .SetVerificationKey(edtConsumerSecret.Text)
    .SetSkipVerificationKeyValidation
    .SetDisableRequireSignature

    // string-based claims validation
    .SetExpectedSubject('paolo-rossi')
    .SetExpectedAudience(True, ['Paolo', 'Luca'])

    // Time-related claims validation
    .SetRequireIssuedAt
    .SetRequireExpirationTime
    .SetEvaluationTime(IncSecond(FNow, 26))
    .SetAllowedClockSkew(20, TJOSETimeUnit.Seconds)
    .SetMaxFutureValidity(20, TJOSETimeUnit.Minutes)

    // Build the consumer object
    .Build()
  );
end;

Token parsing
When you load a compact token in order to validate it, the JOSE-JWT Delphi library parses the compact token and report an error of token malformed if it's not compliant. The checking code recognizes a JWS token (3 parts) and a JWE token (5 parts) although the JOSE-JWT Delphi library doesn't fully implements JWE. The format checking is performed when you assign the compact token representation:

Код:
LSigner := TJWS.Create(LToken); 
  try
    LSigner.SetKey(LKey);

    // This will set off the token parse phase
    LSigner.CompactToken := MyCompactToken;

    Result := LSigner.VerifySignature;
  finally
    LSigner.Free;
  end;

Signature validation
The validation of the signature in an integral part of the JOSE-JWT library and is performed using the TJOSE.Verify() method or invoking the TJWS.VerifySignature() method of the JWS class, this last method has two overloaded versions:

Код:
    function VerifySignature: Boolean; overload;
    function VerifySignature(AKey: TJWK; const ACompactToken: TJOSEBytes): Boolean; overload;

Algorithm checking
You can specify a list of algorithms in the consumer in order to limit the possibility of a downgrade attack. The checking is performed by the TJOSEConsumer, for example: SetExpectedAlgorithms(TJOSEAlgorithmId.HS256, TJOSEAlgorithmId.RS256)

Для просмотра ссылки Войди или ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся
Claims validation

As I said before in a real situation you (the server) want to validate (some of) the claims that are in the token. Some validation could be: token expiration, token issuer, token audience. With this library you can either check for the presence or for a specific value of the claim.

Код:
  // Subject validation
  .SetExpectedSubject('paolo-rossi')

  // Audience validation
  .SetExpectedAudience(True, ['Paolo', 'Luca'])

  // Checks the presence of the iat claim
  .SetRequireIssuedAt

  // Checks the presence of the exp claim
  .SetRequireExpirationTime

  // Sets the evaluation time
  .SetEvaluationTime(IncSecond(FNow, 26))

  // Sets a clock skew for a more relaxed time-based validation
  .SetAllowedClockSkew(20, TJOSETimeUnit.Seconds)

Custom claims validation
The JOSE-JWT library provides a way to validate the standard or private claims through custom code using anonymous methods.
The definition of that anonymous method is the following:

Код:
  TJOSEValidator = reference to function (AJOSEContext: TJOSEContext): string;

Calling the TJOSEBuilder.RegisterValidator(MyCustomValidator) method you can register your custom validator that will be called by the TJOSEConsumer in the process phase. Of course the "standard" validators will be called before your(s). The TJOSEBuilder has another method: SetSkipAllDefaultValidators that allows to skip entirely the standard claims validation and (if you want) to replace entirely the validation process with your code.
In your custom validators you will have access to all the JWT "part" through the TJOSEContext object passed to the custom validator method. The TJOSEContext class, as you can see, has the GetHeader and GetClaims methods that will return all you need. The generic GetClaims<T: TJWTClaims> method will give you the claims object of the desired type.

Код:
  TJOSEContext = class
  public
    function GetJOSEObject: TJOSEParts; overload;
    function GetJOSEObject<T: TJOSEParts>: T; overload;

    function GetHeader: TJWTHeader;

    function GetClaims: TJWTClaims; overload;
    function GetClaims<T: TJWTClaims>: T; overload;
  end;

Demo time?
In the JWTDemo you can find the tabsheet "JWT Consumer (Claim Validation)" that allows you to play with the TJOSEConsumer, TJOSEConsumerBuilder classes and the TJOSEValidator reference.

jwt-demo-consumer.png


Conclusion
In this part we learned how to properly validate a JWT received (in a compact representation) through the TJOSEConsumer and TJOSEConsumerBuilder classes.

Stay tuned for the next part where I will explain in detail the use of custom claims in a JWT token (TJWTClaimsClass class reference).


[/SHOWTOGROUPS]