RickMorpheus
RickMorpheus

Reputation: 11

TIdDecoderMIME.DecodeString loses last decoded character

I want to decode a simple JWT Token in Delphi XE5, but when I do it as shown below, the decodedPayload is missing the final } of the JSON object.

What am I doing wrong?

function getIssuerFromToken(token: string): string;
var
  tokenPayload, decodedTokenPayload, issuer: string;
  tokenJSONValue: TJSONValue;
  tokenJSONWrapper: TKJSONWrapper;
  tokenPayloadStartIndex, tokenPayloadEndIndex, tokenPayLoadLength: integer;
begin
  tokenPayloadStartIndex := Pos('.', token) + 1;
  tokenPayloadEndIndex := Pos('.', token, tokenPayloadStartIndex);
  tokenPayLoadLength := tokenPayloadEndIndex - tokenPayloadStartIndex;

  tokenPayload := Copy(token, tokenPayloadStartIndex, tokenPayLoadLength);
  decodedTokenPayload := TIdDecoderMIME.DecodeString(tokenPayload);
...

Upvotes: 0

Views: 288

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 596166

Per RFC 7519 for the JSON Web Token spec, a JWT token consists of base64url-encoded parts separated by periods. The base64url encoding is described in RFC 7515 for the JSON Web Signature spec. And according to that spec, the base64 is NOT padded:

Base64url Encoding
Base64 encoding using the URL- and filename-safe character set defined in Section 5 of RFC 4648 [RFC4648], with all trailing '=' characters omitted (as permitted by Section 3.2) and without the inclusion of any line breaks, whitespace, or other additional characters. Note that the base64url encoding of the empty octet sequence is the empty string. (See Appendix C for notes on implementing base64url encoding without padding.)

Since base64 requires the input to be an even multiple of 4 characters, you need to add padding to the base64 data before you decode it. You also need to url-decode the base64 before decoding, too. RFC 7515 even provides an example implementation in Appendix C:

static byte [] base64urldecode(string arg)
{
  string s = arg;
  s = s.Replace('-', '+'); // 62nd char of encoding
  s = s.Replace('_', '/'); // 63rd char of encoding
  switch (s.Length % 4) // Pad with trailing '='s
  {
    case 0: break; // No pad chars in this case
    case 2: s += "=="; break; // Two pad chars
    case 3: s += "="; break; // One pad char
    default: throw new System.Exception(
      "Illegal base64url string!");
  }
  return Convert.FromBase64String(s); // Standard base64 decoder
}

In addition, the JSON that is inside the base64 must be UTF-8 encoded, so you have to account for that as well while decoding the base64. The default byte encoding used by TIdDecoderMIME is 8-bit not UTF-8, but its DecodeString() method does have an optional AByteEncoding parameter so you can specify UTF-8.

So, try something like the following in your code:

function decodeJWTpart(const base64: string): string;
var
  s: string;
begin
  s := base64;
  s := StringReplace(s, '-', '+', [rfReplaceAll]);
  s := StringReplace(s, '_', '/', [rfReplaceAll]);
  case Length(s) mod 4 of
    0: ;
    2: s := s + '==';
    3: s := s + '=';
  else
    raise Exception.Create('Illegal base64url string!');
  end;
  Result := TIdDecoder.DecodeString(s, IndyTextEncoding_UTF8);
end;

function getIssuerFromToken(const token: string): string;
var
  tokenPayload, ...: string;
  ...
begin
  ...
  decodedTokenPayload := decodeJWTpart(tokenPayload);
  ...
end;

Upvotes: 1

Related Questions