Recent

Author Topic: [Solved] How to use OAuth2 for Lazarus to get values from Tibber.  (Read 4402 times)

arneolav

  • Full Member
  • ***
  • Posts: 195
    • ElTranslador
First, my thanks to Tonyw for this package.
I'v downloaded the package and Indy.
Compiled the Example, GUI, ok.
But to get it to work with an actual API, seems not that easy.

From the API I have this example:
curl \
-H "Authorization: Bearer 476c477d8a039529478ebd690d35ddd80e3308ffc49b59c65b142321aee963a4" \
-H "Content-Type: application/json" \
-X POST \
-d  '{ "query": "{viewer {homes {currentSubscription {priceInfo {current {total energy tax startsAt }}}}}}" }' https://api.tibber.com/v1-beta/gql

From the above info I have done this:
AuthEndPoint:  https://api.tibber.com/v1-beta/gql
ClientSecret: Bearer 476c477d8a03952.....  (My Private Authorization from the API provider.)
Content-Type: application/json
I'm not sure where to put  the POST
and where  to put the query

I'v got this response:
{"errors":[{"message":"GraphQL operations must contain a non-empty `query` or a `persistedQuery` extension.","extensions":{"code":"INTERNAL_SERVER_ERROR"}}]}
It seems the authorization is accepted.

May be some other information have to be changed?
Any help?
 
« Last Edit: February 07, 2022, 02:51:51 pm by arneolav »
Win XP, Win7, Win 10, Win 11, win64 , Lazarus 3.0RC1
Delphi/DevExpress

PierceNg

  • Sr. Member
  • ****
  • Posts: 369
    • SamadhiWeb
Re: How to use OAuth2 Client for Lazarus?
« Reply #1 on: January 26, 2022, 06:45:12 am »
Your curl example is a POST to an API with bearer authentication. Your text implies that the bearer token is just a secret supplied to you by the API provider. In which case, there is nothing to do with OAuth.

Just implement the POST similarly in Pascal.

Edit: If this is the actual secret, you might want to discard it and get another one from the provider.

rvk

  • Hero Member
  • *****
  • Posts: 6056
Re: How to use OAuth2 Client for Lazarus?
« Reply #2 on: January 26, 2022, 09:00:59 am »
Edit: If this is the actual secret, you might want to discard it and get another one from the provider.
It's a demo token according to the tibby site.
https://developer.tibber.com/explorer

I'm not sure where to put  the POST
and where  to put the query
You need to pass the json query with the post.
What example did you follow for the post?
(Some code you are using would be helpfull)

See an example here:
https://forum.lazarus.freepascal.org/index.php?topic=37563.0

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: How to use OAuth2 Client for Lazarus?
« Reply #3 on: January 26, 2022, 11:31:44 pm »
In OAuth the Bearer token is usually not your client secret. You use your client secret to exchange the activation code you got from the authorization endpoint for the Bearer Token.

At it's core the flow is as follows: The Client (Relying Party, RP) sends the User Agent together with the request to the Operator (OP). This request contains the kind of request (code for the Authentication code flow, token for the implicit flow), the scopes to which access is requested, and a redirect URI for the OP to return back to the RP. OP authenticates the user to an OP specific procedure. OP redirects the user to the Redirect URI, back to the RP,  with  the result of the authentication (error message in case of error, authentication code for the authentication code flow, bearer token for the implicit flow).
In the Implicit flow you are done and can use the Bearer Token for the API Calls as in your example. For the Authentication Code Flow, you (the RP) received an authentication code which needs to be resolved. To do so the RP needs to call the Token endpoint of OP to exchange the code for the bearer token. This includes some form of authentication of the RP against OP. This can either be by using the HTTP Basic Authentication header (base 64 encoded "client_id:client_secret") or by sending the authentication data form encoded as POST body (besides the code) or by using a MAC with the client secret, or by using private key cryptography to sign a blob of data.

So exemplary the flow is something like this:
1) User clicks link to https://oauth.example.com/auth?client_id=xxx&scope=profile&state=yyy&response_type=code&redirect_uri=https%3A%2F%2Frp.example.com%2Fcallback
2) Browser opens url, allows user to login (or use cookie to automatically login)
3) Browser redirects user to the redirect uri "https://rp.example.com/callback" with the response encoded as GET parameters: https://rp.example.com/callback?code=zzz&state=yyy
4) Server at https://example.com/callback checks for the session that is associated to state yyy.
5) Server sends POST request to token endpoint, e.g. at https://oauth.example.com/token with header: "Authorization: Basic AUTH" where AUTH is Base64(client_id + ':' + client_secret) and POST body is "grant_type=authorization_code&code=zzz&redirect_uri=https%3A%2F%2Frp.example.com%2Fcallback" where zzz is the authorization code received in step 3 and redirect_uri is the same as used in step 1
6) OP returns JSON response containing the access token { "access_token": "TOKEN", "token_type": "Bearer", "expires_in": 3600 }

Now after this procedure you can use the access token TOKEN in the "Authorization" header for all the following requests as it is shown in the example you posted.

For futher information I can highly recommend looking at the examples from the OIDC specification. While it is OIDC, OIDC builds upon OAauth2, so there is everything you need to know. Just ignore everything related to the "id_token"
« Last Edit: January 26, 2022, 11:35:56 pm by Warfley »

rvk

  • Hero Member
  • *****
  • Posts: 6056
Re: How to use OAuth2 Client for Lazarus?
« Reply #4 on: January 26, 2022, 11:49:45 pm »
In OAuth the Bearer token is usually not your client secret. You use your client secret to exchange the activation code you got from the authorization endpoint for the Bearer Token.
You described OAuth2 for access to client accounts (other than your own). Tibber also has a "Personal Access Token" which I suspect can be used directly as Bearer token.

See https://developer.tibber.com/docs/guides/calling-api
Quote
To communicate with the API you need to acquire a OAuth access token and pass this along with every request passed to the server. There are two ways of getting hold of an token:

A Personal Access Token give you access to your data and your data only. This is ideal for DIY people that want to leverage the Tibber platform to extend the smartness of their home. Such a token can be acquired here.
.....

If this is for own use for OP, that would be much easier.

yus

  • Jr. Member
  • **
  • Posts: 57
Re: How to use OAuth2 Client for Lazarus?
« Reply #5 on: January 27, 2022, 10:27:52 am »
I implemented OAuth authentication to work with the EVE online game API. You can see an example here https://github.com/seryal/laz-eve-esi/blob/main/src/esiauthorization.pas. I hope it helps.

rvk

  • Hero Member
  • *****
  • Posts: 6056
Re: How to use OAuth2 Client for Lazarus?
« Reply #6 on: January 27, 2022, 10:36:52 am »
OP already has a "Personal Access Token" from the provider. So there is NO NEED for all the OAuth stuff in this case.

ClientSecret: Bearer 476c477d8a03952.....  (My Private Authorization from the API provider.)

Just a correct POST with indy to the service would be sufficient.
OP should just show some code and point to where the troubles begin.

arneolav

  • Full Member
  • ***
  • Posts: 195
    • ElTranslador
Re: How to use OAuth2 Client for Lazarus?
« Reply #7 on: February 05, 2022, 12:25:22 am »
Hi!
sorry, I've been gone a few days
 Thanks for all the suggestions. My intention was to use package:
OAuth2 Client for Lazarus by tonyw:
https://forum.lazarus.freepascal.org/index.php/topic,55962.0.html

But it seems this can be much more simple;

From this Tibber example (same as in the beginning):
curl \
-H "Authorization: Bearer xxxx Example Tokenxxxx4" \
-H "Content-Type: application/json" \
-X POST \
-d  '{ "query": "{viewer {homes {currentSubscription {priceInfo {current {total energy tax startsAt }}}}}}" }' https://api.tibber.com/v1-beta/gql

And after an idea of tudi_x I have done this:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   HTTPS: TFPHTTPClient;
  4.   POSTBody: TStringList;
  5.   DebugURL: string ='https://api.tibber.com/v1-beta/gql';
  6.   err: string;
  7.  
  8. begin
  9.   POSTBody := TStringList.Create;
  10.   POSTBody.Append('{ "query": "{viewer {homes {currentSubscription {priceInfo {current {total energy tax startsAt }}}}}}" }');
  11.   try
  12.     HTTPS := TFPHTTPClient.Create(nil);
  13.     HTTPS.AddHeader('Authorization', 'Bearer ' + '****My Token****');
  14.     HTTPS.AddHeader('x-li-format', 'json');
  15.     HTTPS.AddHeader('Content-Type', 'application/json');
  16.     err := HTTPS.FormPost(DebugURL, POSTBody);
  17.     Memo1.Append(err);
  18.   finally
  19.     FreeAndNil(HTTPS);
  20.     FreeAndNil(POSTBody);
  21.   end;
  22. end;
  23.  
  24.  

Still get this:
{"errors":[{"message":"GraphQL operations must contain a non-empty `query` or a `persistedQuery` extension.","extensions":{"code":"INTERNAL_SERVER_ERROR"}}]}

It seems the PostBody is incorrect, but I can't see what is wrong, any?
« Last Edit: February 05, 2022, 08:07:53 am by arneolav »
Win XP, Win7, Win 10, Win 11, win64 , Lazarus 3.0RC1
Delphi/DevExpress

rvk

  • Hero Member
  • *****
  • Posts: 6056
Re: How to use OAuth2 Client for Lazarus?
« Reply #8 on: February 05, 2022, 10:28:33 am »
err := HTTPS.FormPost(DebugURL, POSTBody);
...
It seems the PostBody is incorrect, but I can't see what is wrong, any?
You are using FormPost to post the query. But FormPost restructures your posted data into field1=field1-data&field2=field2-data etc. and that gets posted.

For Tibber you just need to put the single query into the requestbody without form= layout.

The code below works fine (the key I used is the demo-key from the Tibby developer website and works fine for testing).

Code: Pascal  [Select][+][-]
  1. uses FPHTTPClient, opensslsockets;
  2.  
  3. procedure TForm1.Button1Click(Sender: TObject);
  4. var
  5.   HTTPS: TFPHTTPClient;
  6.   URL: string ='https://api.tibber.com/v1-beta/gql';
  7.   myQuery: string = '{ "query": "{viewer {homes {currentSubscription {priceInfo {current {total energy tax startsAt }}}}}}" }';
  8.   myResult: string;
  9. begin
  10.   HTTPS := TFPHTTPClient.Create(nil);
  11.   try
  12.     HTTPS.AddHeader('Authorization', 'Bearer ' + '476c477d8a039529478ebd690d35ddd80e3308ffc49b59c65b142321aee963a4');
  13.     HTTPS.AddHeader('Content-Type', 'application/json');
  14.     HTTPS.RequestBody := TRawByteStringStream.Create(MyQuery);
  15.     myResult := HTTPS.Post(URL);
  16.     Memo1.Append(myResult);
  17.   finally
  18.     HTTPS.RequestBody.Free;
  19.     HTTPS.Free;
  20.   end;
  21. end;

arneolav

  • Full Member
  • ***
  • Posts: 195
    • ElTranslador
Re: How to use OAuth2 for Lazarus?
« Reply #9 on: February 05, 2022, 10:38:15 am »
Thanks a lot, it works ok!

Got this result:
Memo1
{"data":{"viewer":{"homes":[{"currentSubscription":{"priceInfo":{"current":{"total":1.4904,"energy":1.1843,"tax":0.3061,"startsAt":"2022-02-05T11:00:00.000+01:00"}}}},{"currentSubscription":{"priceInfo":{"current":{"total":1.4904,"energy":1.1843,"tax":0.3061,"startsAt":"2022-02-05T11:00:00.000+01:00"}}}}]}}}

Then I have to move from XML to Json.
« Last Edit: February 06, 2022, 04:06:26 pm by arneolav »
Win XP, Win7, Win 10, Win 11, win64 , Lazarus 3.0RC1
Delphi/DevExpress

arneolav

  • Full Member
  • ***
  • Posts: 195
    • ElTranslador
Re: How to use OAuth2 for Lazarus?
« Reply #10 on: February 07, 2022, 02:50:36 pm »
Thanks to avk we can decode Tibber this way:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. type
  3.   TData = record
  4.     total,
  5.     energy,
  6.     tax: double;
  7.     startsAt: string;
  8.   end;
  9. var
  10.   s: string;
  11.   d, o: TJsonData;
  12.   ja: TJsonArray;
  13.   jo: TJsonObject;
  14.   a: array of TData;
  15.   I: Integer;
  16. begin
  17. // put your values from Tibber in s:
  18.   s := '{"data":{"viewer":{"homes":[{"currentSubscription":{"priceInfo":{"current":{"total":1.4904,"energy":1.1843,"tax":0.3061,"startsAt":"2022-02-05T11:00:00.000+01:00"}}}},{"currentSubscription":{"priceInfo":{"current":{"total":1.4904,"energy":1.1843,"tax":0.3061,"startsAt":"2022-02-05T11:00:00.000+01:00"}}}}]}}}';
  19.   d := GetJSON(s);
  20.   o := d.FindPath('data.viewer.homes');
  21.   if (o <> nil) and (o.JSONType = jtArray) then begin
  22.     ja := TJsonArray(o);
  23.     SetLength(a, ja.Count);
  24.     for I := 0 to ja.Count - 1 do begin
  25.       o := ja.Objects[I].FindPath('currentSubscription.priceInfo.current');
  26.       if (o <> nil) and (o.JSONType = jtObject) then begin
  27.         jo := TJsonObject(TJsonObject(o));
  28.         with a[I] do begin
  29.           total := jo.Floats['total'];
  30.           energy := jo.Floats['energy'];
  31.           tax := jo.Floats['tax'];
  32.           startsAt := jo.Strings['startsAt'];
  33.         end;
  34.       end;
  35.     end;
  36.   end;
  37.   d.Free;
  38.  
  39.   for I := 0 to High(a) do begin
  40.     Memo1.Append('Item ' + I.ToString);
  41.     Memo1.Append('  total: ' + a[I].total.ToString);
  42.     Memo1.Append('  energy: ' + a[I].energy.ToString);
  43.     Memo1.Append('  tax: ' + a[I].tax.ToString);
  44.     Memo1.Append('  startsAt: ' + a[I].startsAt);
  45.   end;
  46. end;  
Win XP, Win7, Win 10, Win 11, win64 , Lazarus 3.0RC1
Delphi/DevExpress

 

TinyPortal © 2005-2018