I need to get google OA2.0 access token for service account using JWT and schannel (not openssl - it is working, but I have to use schannel).
Using win10, Delphi Alexandria 11.3 Patch 1, Indy 10... Libraries for SChannel: https://github.com/tothpaul/Delphi/tree/master/Indy.SChannel
My code(not working):
function TForm1.IdGetAccessTokenSChannel(GServiceAccount, GKey, GScope: string;
var GAccessToken: string; var GAccessTokenExpiry: TDateTime;
var ErrMsg: string): boolean;
var
LToken: TJWT;
LKey: TJWK;
LJWS: TJWS;
LDT: TDateTime;
LCompactedToken: TJOSEBytes;
IdHTTP: TIdHTTP;
IdSSLIOHandlerSocketSChannel: TIdSSLIOHandlerSocketSChannel;
InSS, OutSS: TStringStream;
Response: string;
LJSONResponse: TJSONObject;
Expiry: integer;
JSONstr: string;
begin
Result := false;
GAccessToken := '';
GAccessTokenExpiry := 0.0;
ErrMsg := '';
LToken := TJWT.Create;
try
LToken.Claims.SetClaimOfType('iss', GServiceAccount);
LToken.Claims.SetClaimOfType('scope', GScope);
LToken.Claims.Audience := 'https://oauth2.googleapis.com/token';
LToken.Claims.IssuedAt := Now;
LDT := Now;
LDT := IncMinute(LDT, 30);
LToken.Claims.Expiration := LDT;
LKey := TJWK.Create(GKey);
try
LJWS := TJWS.Create(LToken);
try
LJWS.Sign(LKey, TJOSEAlgorithmId.RS256);
LCompactedToken := LJWS.CompactToken;
finally
LJWS.Free;
end;
finally
LKey.Free;
end;
finally
LToken.Free;
end;
IdHTTP := TIdHTTP.Create(nil);
try
// Set up SSL
IdSSLIOHandlerSocketSChannel := TIdSSLIOHandlerSocketSChannel.Create(IdHTTP);
IdHTTP.IOHandler := IdSSLIOHandlerSocketSChannel;
InSS := TStringStream.Create(
'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion='+
LCompactedToken.AsString// , TEncoding.UTF8
);
OutSS := TStringStream.Create;
try
IdHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
try
InSS.Position := 0;
IdHTTP.Post(
'https://oauth2.googleapis.com/token',
//'https://www.googleapis.com/oauth2/v4/token',
InSS, OutSS
);
Response := OutSS.DataString;
JSONstr := Response;
LJSONResponse := TJSONObject.ParseJSONValue(JSONstr) as TJSONObject;
if LJSONResponse <> nil then
begin
try
try
if (LJSONResponse.Values['access_token'].Value <> '') and
(LJSONResponse.Values['expires_in'].Value <> '') then
begin
if TryStrToInt(LJSONResponse.Values['expires_in'].Value, Expiry) then
begin
if Expiry > 1 then
begin
GAccessToken := LJSONResponse.Values['access_token'].Value;
GAccessTokenExpiry := IncSecond(Now, Expiry);
Result := true;
end;
end;
end;
except
on E: Exception do
ErrMsg := 'JSON read error: '+E.Message;
end;
finally
LJSONResponse.Free;
end;
end
else
ErrMsg := 'returned invalid JSON';
except
on E: Exception do
ErrMsg := 'POST call to googleapis error: '+E.Message;
end;
finally
InSS.Free;
OutSS.Free;
end;
finally
IdHTTP.Free;
end;
end;
I receive only this error: Read timed out...
But when I exchange this code:
IdSSLIOHandlerSocketSChannel := TIdSSLIOHandlerSocketSChannel.Create(IdHTTP);
IdHTTP.IOHandler := IdSSLIOHandlerSocketSChannel;
with this (openSSL):
IdSSLIOHandlerSocketOpenSSL := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP);
with IdSSLIOHandlerSocketOpenSSL.SSLOptions do
begin
Method := sslvTLSv1_2;
Mode := sslmClient;
SSLVersions := [sslvTLSv1_2];
end;
IdHTTP.IOHandler := IdSSLIOHandlerSocketOpenSSL;
everything starts to work. Any other google API calls are working with SChannel, only getting access token using JWT no. I tried also another URLs: https://www.googleapis.com/oauth2/v2(v3,v4)/token
- with no success
Any help - explanation is welcome. Thank you!
Edit 6.8.2023 - Solved (partially): Author of Indy.Schannel wrote me: Hi, I've found the bug, in fact the response is read, but my component returns an invalide information to Indy which throws an exception. you can fix the problem by changing line 1075 of Execute.SChannel, Info.Readable = 0 is acceptable, so change ">" to ">=" and voilĂ !
function SSLPending(SSL: THandle; AMSec: Integer): Boolean;
var
Info: PSSLInfo absolute SSL;
begin
Result := (Info <> nil) and (Info.Readable(AMSec) >= 0);
end;
It's happens on this server because it doesn't return a Content-Length nor use a chunked reply...so Indy has to read until the connexion is closed (Readable = 0)
Regards Paul
So thanks Paul!!!
Unfortunately, it turned out that TJWS.Sign from delphi-jose-jwt also uses the openSSL library (RS256 required by Google) to sign with the TJOSEAlgorithmId.RS256 algorithm, so I've made some progress, but I still don't have a solution... Perhaps using BCrypt? Well, I'll keep looking. If anyone knew?