I must be missing something here!
I have been playing around trying to refresh an expired OAUTH2 token using the new ( new to me anyway, coming from delphi xe2 environment) TOAuth2Authenticator, TRESTClient, TRESTRequest, TRESTResponse components
I have set the following authenticator properties with the existing known values for
ClientID
ClientSecret
Scope
AccessTokenEndPoint
AuthorizationEndPoint
RedirectionEndPoint
AccessToken
AccessTokenExpiry
RefreshToken
and can successful access resources from the REST server, up until the token expires.
I presumed (wrongly, so it seems) if I try an execute a request against a server, and the token had expired, there should be enough detail for the component to realise the token has expired and refresh it as and when it need to.
I take it there is no hidden/undocumented "RefreshExpiredToken" method that I can call?
Any pointers in the right direction would be greatly appreciated :-)
Thanks
I eventually figured this out, by bastardising the publicTOAuth2Authticator.ChangeAuthCodeToAccessToken procedure, but thought I'd post my solution just in case it helps anyone else out:
LClient := TRestClient.Create(AccessTokenURI);
try
LRequest := TRESTRequest.Create(LClient); // The LClient now "owns" the Request and will free it.
LRequest.Method := TRESTRequestMethod.rmPOST;
LSecretBase64 := String(SZFullEncodeBase64(AnsiString(<myClientID>+ ':' + <MyClientSecret>)));
LRequest.AddAuthParameter('grant_type', 'refresh_token', TRESTRequestParameterKind.pkGETorPOST);
LRequest.AddAuthParameter('refresh_token', _AccessRefreshToken, TRESTRequestParameterKind.pkGETorPOST);
LRequest.AddAuthParameter('Authorization','Basic '+LSecretBase64, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode] );
LRequest.Execute;
//Memo1.Lines.Add(LRequest.Response.Content);
if LRequest.Response.GetSimpleValue('access_token', LToken) then
begin
_AccessToken := LToken;
end;
if LRequest.Response.GetSimpleValue('refresh_token', LToken) then
begin
_AccessRefreshToken := LToken;
//Memo1.Lines.Add('RefreshExpiredToken: New Refresh Token Extracted');
end;
// detect token-type. this is important for how using it later
if LRequest.Response.GetSimpleValue('token_type', LToken)
then _TokenType := OAuth2TokenTypeFromString(LToken);
// if provided by the service, the field "expires_in" contains
// the number of seconds an access-token will be valid
if LRequest.Response.GetSimpleValue('expires_in', LToken) then
begin
LIntValue := StrToIntdef(LToken, -1);
if (LIntValue > -1) then
_AccessTokenExpireDT := IncSecond(Now, LIntValue)
else
_AccessTokenExpireDT := 0.0;
//Memo1.Lines.Add('RefreshExpiredToken: New Token Expires '+formatdatetime('hh:nn:ss dd/mm/yyyy', _AccessTokenExpireDT));
end;
finally
LClient.DisposeOf;
end;
Related
I'm having trouble with a POST request to an API which I am not the owner of.
The request must simply post JSON data. Please have a look:
var
RESTRequest : TRESTRequest;
RESTClient : TRESTClient;
Response : TRESTResponse;
contract : TJSONObject;
begin
RESTClient := TRESTClient.Create('URL');
try
RESTRequest := TRESTRequest.Create(nil);
try
contract := TJSONObject.Create;
contract.AddPair(TJSONPair.Create('name','my_first_contract.pdf'));
RESTRequest.Client := RESTClient;
RESTRequest.Method := rmPOST;
RESTRequest.Accept := 'application/json';
RESTRequest.AddParameter('j_token','mytoken',pkHTTPHEADER,poDoNotEncode);
RESTRequest.AddBody(contract);
RESTRequest.Execute;
Response := RESTRequest.Response;
ShowMessage(Response.StatusText + ' : ' + Response.Content);
finally
RESTRequest.Free;
end;
finally
RESTClient.Free;
end;
end;
I obtained this error :
Not Found : {"errors":"Fatal error in JsonConvert. Passed parameter json object in JsonConvert.deserializeObject() is not of type object.\n"}
I've read online that the AddBody() method first serializes its content if it's an object. In this case, the content of the body is my TJSONObject, but when I try to replace that with a String, like this:
var
contract : String;
...
begin
contract := '{"name":"my_first_contract.pdf"}';
...
RESTRequest.AddBody(contract, ctAPPLICATION_JSON);
...
end;
I'm getting the exact same error.
So, does that mean that a TJSONObject is not viewed as an Object for the JsonConvert.deserializeObject() method ? Or, is the serialization of the AddBody() messed up?
The problem was on the 'j_token' header : as I was trying to solve it, some friends wanted to help me but I didn't want to give them the access token because it's exclusive to my company. They still tried to access the api with a false token wich resulted with the same error as I was getting :
Not Found : {"errors":"Fatal error in JsonConvert. Passed parameter json object in JsonConvert.deserializeObject() is not of type object.\n"}
Thanks to that I could deduce that the issue was on the j_token. After setting up my own api I could watch what was I posting and then I saw that my 'j_token' header was still getting encoded even though I added the poDoNotEncode options to my AddParameter method.
I created a new post on this forum to look for that poDoNotEncode error if you ever stumble upon this same problem : Trouble with poDoNotEncode option in TRESTRequest.AddParameter() method
I'm currently building a restful API server for my mobile devices to access a mySQL database. The mobile apps need to be able to get data from the server, and also insert and edit data.
I have managed to successfuly do a GET from the server, but I'm stuck at how to get do the post. On the WebModuleUnit I added an action, with the following properties:
MethodType mtGet
Name actImsDocuments
PathInfo /imsDocuments
procedure TWebModule1.WebModule1actImsDocumentsAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
qryDocuments: TFDQuery;
JsonArray: TJSONArray;
JsonObject: TJSONObject;
begin
// Configure response
Response.ContentType := 'application/json; charset=utf-8';
//Search for my clients table
qryDocuments := TFDQuery.Create(nil);
with qryDocuments do
begin
Connection := FDConnection1;
Active := false;
SQL.Clear;
Open('SELECT * FROM ims_document');
if qryDocuments.RecordCount > 0 then //if there is items
begin
JsonArray := TJSONArray.Create;
try
First;
while not EoF do //Iterate all items in the FDQuery
begin
JsonObject := TJSONObject.Create;
AddFieldsToJSON(JsonObject, qryDocuments);
JsonArray.AddElement(JsonObject);
Next;
end;
finally
Response.Content := JsonArray.ToString;
JsonArray.DisposeOf;
end;
end;
end;
end;
procedure TWebModule1.AddFieldsToJSON(AJsonObject: TJSONObject;
AQuery: TFDQuery);
begin
with AQuery do
begin
AJsonObject.AddPair('number', TJSONNumber.Create(FieldByName('number').AsInteger));
AJsonObject.AddPair('document_title', FieldByName('document_title').AsString);
end;
end;
Now this all above works perfectly, what I have no idea how to do, is how do I for example, edit a record in the table, or insert a new record, how do I get the values from the mobile device. I have no idea how to do a post, or update. Can anyone just give me an example of code please, as I have looked everywhere and just cannot get a clear understanding of this.
If I change the property of the action to mtPost, how would my code differ If I wanted to add a record to the table?
I am trying to insert an event into my Google Calendar via the Delphi REST controls, but I am uncertain where to add the access token to my request. My Code looks like this:
var
evt : String;
begin
ResetRESTComponentsToDefaults;
RESTClient.BaseURL := 'https://www.googleapis.com/calendar/v3';
RESTClient.Authenticator := OAuth2_GoogleCalendar;
RESTRequest.Resource := 'calendars/primary/events';
evt:='{"summary":"test","description":"test","id":"06824945162f4204bfdc041ae1bbae85","start":{"date":"2018-04-10"},"end":{"date":"2018-04-10"},"guestsCanInviteOthers":false,"visibility":"private"}'
RESTRequest.AddParameter('access_token',OAuth2_GoogleTasks.AccessToken,pkHTTPHEADER);
RESTRequest.Method := TRESTRequestMethod.rmPOST;
RESTRequest.Body.Add(evt,ctAPPLICATION_JSON);
RESTRequest.Execute;
end;
The scope is https://www.googleapis.com/auth/calendar
If I send it in like this I this error:
{
"error":
{
"errors":
[
{
"domain":"global",
"reason":"required",
"message":"Login Required",
"locationType":"header",
"location":"Authorization"
}
]
,
"code":401,
"message":"Login Required"
}
}
Appending ?access_token={accessToken} to the end of the url i get
error 400, parseError.
Where should I add the access token to the request?
I cant help much with Delphi its been years since i have used it, but you have a two options with regard to adding your access token.
First is to just add it as a parameter on the base url
?access_token=TokenHere
The second option is to send it as an authorization header on your request its a bearer token.
Authorization : Bearer cn389ncoiwuencr
After a bit of googling i found this
FIdHTTP.Request.CustomHeaders.FoldLines := False;
FIdHTTP.Request.CustomHeaders.Add('Authorization:Bearer ' + txtToken.Text);
I am trying to insert an event into my google calendar using the delphi REST controls.
This is the code so far:
procedure TForm1.TestGoogleRestParams;
var
i: Integer;
jsonObjEventResource,jsonObjStart,jsonObjEnd: TJSONObject;
begin
try
jsonObjEventResource:=TJSONObject.Create();
jsonObjStart:=TJSONObject.Create();
jsonObjEnd:=TJSONObject.Create();
jsonObjEventResource.AddPair(TJSONPair.Create('summary','test'));
jsonObjEventResource.AddPair(TJSONPair.Create('description','Testing'));
jsonObjEventResource.AddPair(TJSONPair.Create('id',LowerCase('06824945162F4204BFDC041AE1BBAE85')));
jsonObjStart.AddPair(TJSONPair.Create('date',FormatDateTime('yyyy-mm-dd',Now)));
jsonObjEventResource.AddPair(TJSONPair.Create('start',jsonObjStart));
jsonObjEnd.AddPair(TJSONPair.Create('date',FormatDateTime('yyyy-mm-dd',Now)));
jsonObjEventResource.AddPair(TJSONPair.Create('end',jsonObjEnd));
jsonObjEventResource.AddPair(TJSONPair.Create('guestsCanInviteOthers',TJSONBool. Create(false)));
jsonObjEventResource.AddPair(TJSONPair.Create('visibility','private'));
mem_Test.Lines.Add(TJson.Format(jsonObjEventResource));
//mem_Test.Lines.Add(jsonObjEventResource.ToJSON);
RESTRequest.Method := TRESTRequestMethod.rmPOST;
RESTRequest.Body.ClearBody;
RESTRequest.AddBody(jsonObjEventResource);
RESTRequest.Execute;
finally
//jsonObjEventResource.Free;
//jsonObjStart.Free;
//jsonObjEnd.Free;
end;
end;
The Scope I am using is: https://www.googleapis.com/auth/calendar.
BaseURL : https://www.googleapis.com/calendar/v3
ResourceURI : calendars/primary/events
I do get an access token and a refresh token but I cannot Post the Request. This is the error i recieve:
{
"error":
{
"errors":
[
{
"domain":"global",
"reason":"required",
"message":"Login Required",
"locationType":"header",
"location":"Authorization"
}
]
,
"code":401,
"message":"Login Required"
}
}
With the following uri: https://www.googleapis.com/calendar/v3/calendars/primary/events
How can i fix this?
If I don't call this method and just call RESTRequest.Execute; I get a list of all my existing events.
":"Invalid Credentials"
Means that the client id and client secret that you are using are incorrect. You should download a new file from Google Developer console and try again.
Option 2:
Try authenticating the user it could be that the access token has expired and needs to be refreshed
I am using auth0 and golang for a rest service that is similar implemented as shown here.
I wonder how I can find out the name of the user that is currently triggering a certain API call - for instance if someone requests http://localhost:3000/products - the go handler in this case looks like this:
var ProductsHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
payload, _ := json.Marshal(products)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(payload))
})
Does the request r contain more information about the current user?
Or do I need to find out the current user in the middleware authentication:
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
secret := []byte("{YOUR-AUTH0-API-SECRET}")
secretProvider := auth0.NewKeyProvider(secret)
audience := "{YOUR-AUTH0-API-AUDIENCE}"
configuration := auth0.NewConfiguration(secretProvider, audience, "https://{YOUR-AUTH0-DOMAIN}.auth0.com/", jose.HS256)
validator := auth0.NewValidator(configuration)
token, err := validator.ValidateRequest(r)
if err != nil {
fmt.Println(err)
fmt.Println("Token is not valid:", token)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Unauthorized"))
} else {
next.ServeHTTP(w, r)
}
})
}
Does the token contain more information about the user?
I am a bit lost here. auth0 works perfectly to ensure that only registered persons can use the REST-API, but I want to deliver user specific information. So it depends on the current user what a REST call is handing back. Initially, I was thinking that auth0 would take care of this. Is there a simple way to achieve this?
Yes, you need to use token to get information about request issue.
To sort all you want you need to take a look to next:
Check out how token extracted in this method: token extraction
And the Claims here: Claims structure
And how combine it here: retrieve Claims
The claims have a field
Issuer string `json:"iss,omitempty"`
you are interested in.