Expire-date in refresh-token (oauth2, exact) - oauth-2.0

So I'm using the exact-online XML-api to retrieve some user-related data.
This works fine so far, but since it is using oauth 2.0, I am being redirected after 600 sec and the login-prompt appears again.
This function refreshes the access-token:
/**
* #param string $refreshToken
* #return array {access_token, expires_in, refresh_token}
*/
public function refreshAccessToken($refreshToken)
{
$params = array(
'refresh_token' => $refreshToken,
'grant_type' => self::GRANT_REFRESH_TOKEN,
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret
);
$url = sprintf(self::URL_TOKEN, $this->countryCode);
return $this->getResponse($url, $params);
}
I debuged into it and the expires_in is set to 600. I guess this is the cause I'm being logged after after a short amount. My question: how can I disable this 600 sec timeout?
Also I found this function:
/**
* #param int $expiresInTime
*/
protected function setExpiresIn($expiresInTime)
{
$this->expiresIn = time() + $expiresInTime;
}
I modified the function and added a *1000 so it doesn't run out, but that didn't affect the outcome.
Is this an oauth-specific thing? Is it somehow managable to don't be kicked off after 10 mins?
There is no such thing in the backend of exact-online, which could change this value. Also, since this is a request I recieve, I don't think I can manipulate it, right?
Update 1
Due to the good feedback, I think I got a start with that problem, but no solution yet. I'll provide the code I'm currently using. I also asked the support of exact, but didn't got too much help there unfortunatly.
Please see this code:
<?php
require_once($sShopHomeDir . "modules/" . $sModulePath . "/library//exact/ExactApi.php");
$this->_exactApi = new ExactApi('de', $this->_sClientId, $this->_sClientSecret, $this->_sDivision);
$this->_exactApi->getOAuthClient()->setRedirectUri($this->_sRedirectUri.$param);
if (!isset($_GET['code']))
{
// Redirect to Auth-endpoint
$authUrl = $this->_exactApi->getOAuthClient()->getAuthenticationUrl();
header('Location: ' . $authUrl, true, 302);
die('Redirect');
}
else
{
$tokenResult = $this->_exactApi->getOAuthClient()->getAccessToken($_GET['code']);
$this->_exactApi->setRefreshToken($tokenResult['refresh_token']);
}
From the comments and answer, I'm convinced that this is the way to go. I'm retrieving a code and store/set the refresh-token - which, from my understanding, is correct that way.
For a better readabilty, I'll provide links to the two files which Exact online provides in their examples:
ExactApi.php
ExactOAuth.php

You have two OAuth flows supported by Exact Online. From the information and the timeout you give I am pretty sure you are using the implicit grant flow, which is not documented by Exact, but it does work.
The only other option you have is to use the token based authentication flow, which requires an extra step, namely exchanging your response code for an access token. That token is valid for a year and can be refreshed afterwards to extend the period for another year. The token based authentication flow is only useful in environments you can control, like web applications.
That token based authentication flow doesn't work with two phase authentication, so you can't refresh a token after that one year period.

Related

How to not make the user re-login every time browser closes?

This might be a basic/dumb question, but I don't know the right keyword to Google. What I want is that when I authenticate the user to my web app, they can close the browser, and when they open it back, they can still use my website - they are not logged out (yet).
I have been following the tutorials in IdentityServer docs (https://identityserver4.readthedocs.io/en/latest/quickstarts/2_interactive_aspnetcore.html), and so far I have managed to get the whole IDP-API-Client working. I have inspect the token that I get from IDP, it's valid for 2 weeks, so what am I missing here, why do I get logged out when I close the browser?
My guess is that I need to store the token to the cookie, but how do I save it, and how do I force the web application to always check for the cookie?
The IS4 tutorial has this:
services.AddAuthentication(o =>
{
o.DefaultScheme = "Cookies";
o.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", o =>
{
o.Authority = "https://localhost:5001";
o.ClientId = "mymvcclient";
o.ClientSecret = "mymvcclientsecret";
o.ResponseType = "code";
o.SaveTokens = true;
o.Scope.Add("myapi");
o.Scope.Add("offline_access");
});
I assume that's just creating a cookie, but how do I specify for it to save my token, and read the token from the cookie when user opens my web application?
Yea there is an option called ExpireTimeSpan in your cookie handler, which defaults to 14 days. You can change it to anytime longer than that by just setting a value to it:
...
.AddCookie("Cookies", options => {
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = false;
})
.AddOpenIdConnect("oidc", options => {
...
options.UseTokenLifetime = false;
...
})...
The above sets the cookie expiration to 30 days, instead of the default 2 weeks.
You also want to make sure the UseTokenLifetime option from the OIDC is set to false, which is the default for now I think. Tokens coming back from the Identity Server, for example, tend to have short lives. If you set it to true, which was default before, then it would override whatever expiration you set earlier.
The best term to look for on this is probably "persistent sessions", or just session management in general. This is something handled by ASP.NET Core, not really IdentityServer. There are several mechanisms to maintain session state on the server, which you'd need for example not to lose all sessions when a server restart happens.
I've had the best luck using the ITicketStore interface, which allows persisting the sessions to a database. The cookie ends up with a session ID which is validated on each request for expiration.

Discord Oauth2 receiving 'invalid client' error

I had Discord Oauth2 implemented so that my users could log into my website by authenticating through Discord. For months, everything worked great and now all of the sudden it stopped working.
Per Discord's oauth2 instructions,https://discordapp.com/developers/docs/topics/oauth2#shared-resources, I am able to successfully acquire the access code that is meant to be traded for the access token. However, when I try to receive the access token I receive an 'invalid_client' error.
First, I am hitting this endpoint:
https://discordapp.com/api/oauth2/authorize?client_id=${process.env.CLIENT_ID}&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Flogin%2Fdiscord%2Fcallback&response_type=code&scope=identify%20email%20gdm.join
which successfully returns the following:
http://localhost:5000/login/discord/callback?code={some_access_code}
The access code is then sent back to discord to obtain the access token. Here is the code that is failing:
export function getDiscordAccessToken(accessCode, call) {
const redirect = call === 'login' ? process.env.DISCORD_LOGIN_REDIRECT : process.env.DISCORD_CONNECT_REDIRECT
return new Promise((resolve, reject) => {
axios
.post(
`https://discordapp.com/api/oauth2/token?client_id=${process.env.DISCORD_CLIENTID}&client_secret=${process.env.DISCORD_SECRET}&grant_type=authorization_code&code=${accessCode}&redirect_uri=${redirect}&scope=identify%20email%20gdm.join`
)
.then(res => {
resolve(res.data)
})
.catch(err => {
// log error to db
console.log("Here is your error: ", err.response)
reject(err.response)
})
})
}
This code was working for months with no problems. Then, all of the sudden it stopped working. I even checked the Discord change logs which can be found here, https://discordapp.com/developers/docs/change-log, but I found no reference to authentication changes.
Any help you can provide is greatly appreciated!
The query parameters should be in the BODY of the POST request, not the URL for the oauth/token url.
Discord recently pushed a update to the oAuth2 which makes it confine more with the standard. This means they no longer support parameters in the URL for POST, but instead require them to be in the body and form encoded (basically the same, but in the body and without the leading ?).
So you basically need (not tested):
axios.post(
`https://discordapp.com/api/oauth2/token`,
`client_id=${process.env.DISCORD_CLIENTID}&client_secret=${process.env.DISCORD_SECRET}&grant_type=client_credentials&code=${accessCode}&redirect_uri=${redirect}&scope=identify%20email%20gdm.join`
)
I know the question has already been answered, but in my case I copied a wrong secret key. Just make sure that you copy the right one.
Secret Key is located under OAuth2 Tab and not under General Information tab on discord developer's dashboard.

Can you reuse a Google service account access token?

Using the Google API PHP Client with a service account, it returns an array like the following:
array (size=3)
'access_token' => string '...TOKEN...' (length=127)
'token_type' => string 'Bearer' (length=6)
'expires_in' => int 3600
Is it best practice to generate a new token every page request? It seems wasteful to do so when each token is valid for one hour. But since the token does not include a created value or a token_id value, the builtin isAccessTokenExpired() method will always return true, meaning it is always expired.
I see several options for reusing the same token:
Option 1: When token is created via fetchAccessTokenWithAssertion(), I can manually add a created value to the token array with time() as its value. Then save that to session/database so later when isAccessTokenExpired is called it will have that field to verify.
Option 2: Save token to session/database along with the timestamp the token will expire (time() + $token['expires_in']), and then on subsequent views I can do my own calculations to verify that the token is still in a valid time period. This seems a bit weird too though as I can't be fully sure that Google has not revoked the token or anything funny like that.
Option 3: Call a method that uses the access token and checks its response. If the call succeeded then the access token must be good still, if not then I can go ahead and ask for a new one. But what method could I call? One that would only need the most basic permissions would be good.
Thank you.
You can request a new token each time, but it's needless overhead and I believe it can eat into your API call quota as well.
I basically do #2, but I also subtract 10 seconds off of the expires_in just to be certain I don't have a request made with a just-expired token. TBH there's no reason that Google would revoke an individual token that wouldn't result in full revocation of all access, that's kind of why their token lifetime is so short to begin with.
I don't use the official API client, but the Cliff's Notes version of my logic is:
class Token {
public function __construct($info) {
$this->token = $info['access_token'];
$this->type = $info['token_type'];
$this->expiry = time() + $info['expires_in'] - 10;
}
public function isValid() {
return ($this->validFor()) > 0;
}
public function validFor() {
return $this->expiry - time();
}
}
class TokenFactory implements TokenFactoryInterface {
public function token($force_new=false) {
if( $force_new || !isset($this->token) || !$this->token->isValid() ) {
$this->token = $this->newToken();
}
return $this->token;
}
}

Google realtime Model update onFileLoad

I see that the workflow is to start authrorizer, giving it file loader. So, we have a sequence of callbacks, onAuthrorized => start loading file => doc.getModel() on file load. Here they say how you get the model. But, I also see that gapi.drive.realtime.load(fileId, onFileLoaded, initializeModel, handleErrors) can elso end up with TOKEN_REFRESH_REQUIRED and it seems that TOKEN_REFRESH_REQUIRED can fire after the document is loaded, after some time of user inactivity, which seems to be related with token expiration. How should re-authorization to go? Should I tell the client that the current model that he is connected to is invalid? Please note that my app starts on file load. So, if I go the whole stack re-authorization, which calls another file load, which calls another document loaded will re-start my application. Is it supposed way to go? To put in other words, is there a way to refresh the token without loosing existing connection?
Where is the token stored actually? I do not see that I receive it on authorized. It is not passed to the realtime.load. How does realtime.load knows about the token? How can I speed up the token expiration for debug?
I am still not sure that this is a right answer but this is what I have got looking at code here, which says that we should provide empty callback to re-authorize
/**
* Reauthorize the client with no callback (used for authorization failure).
* #param onAuthComplete {Function} to call once authorization has completed.
*/
rtclient.Authorizer.prototype.authorize = function(onAuthComplete) {
function authorize() {
gapi.auth.authorize({client_id: rtclient.id, scope: ['install', 'file'],}, handleAuthResult)
}
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
hideAuthorizationButton() && onAuthComplete()
} else with (authorizationButton) {
display = 'block' ;
onclick = authorize;
}
}
You call it first use it in a function to load your document
(rtclient.authorizer ? rtclient.authorizer = identity : rtclient.authorize) (proceedToLoadingTheFile)
But later, on timeout we have code
function handleErrors(e) { with(gapi.drive.realtime.ErrorType) {switch(e.type) {
case TOKEN_REFRESH_REQUIRED: rtclient.authorizer.authorize() ; break
case CLIENT_ERROR: ...
Note no arguemnts in the latter. Authorizer won't reload the document. I think that this explains the logic asked. However, it does not answer about the internals, how is it possible that loader takes on existing authorizer or switches to a new one.

Best way to upload files to Box.com programmatically

I've read the whole Box.com developers api guide and spent hours on the web researching this particular question but I can't seem to find a definitive answer and I don't want to start creating a solution if I'm going down the wrong path. We have a production environment where as once we are finished working with files our production software system zips them up and saves them into a local server directory for archival purposes. This local path cannot be changed. My question is how can I programmatically upload these files to our Box.com account so we can archive these on the cloud? Everything I've read regarding this involves using OAuth2 to gain access to our account which I understand but it also requires the user to login. Since this is an internal process that is NOT exposed to outside users I want to be able to automate this otherwise it would not be feasable for us. I have no issues creating the programs to trigger everytime a new files gets saved all I need is to streamline the Box.com access.
I just went through the exact same set of questions and found out that currently you CANNOT bypass the OAuth process. However, their refresh token is now valid for 60 days which should make any custom setup a bit more sturdy. I still think, though, that having to use OAuth for an Enterprise setup is a very brittle implementation -- for the exact reason you stated: it's not feasible for some middleware application to have to rely on an OAuth authentication process.
My Solution:
Here's what I came up with. The following are the same steps as outlined in various box API docs and videos:
use this URL https://www.box.com/api/oauth2/authorize?response_type=code&client_id=[YOUR_CLIENT_ID]&state=[box-generated_state_security_token]
(go to https://developers.box.com/oauth/ to find the original one)
paste that URL into the browser and GO
authenticate and grant access
grab the resulting URL: http://0.0.0.0/?state=[box-generated_state_security_token]&code=[SOME_CODE]
and note the "code=" value.
open POSTMAN or Fiddler (or some other HTTP sniffer) and enter the following:
URL: https://www.box.com/api/oauth2/token
create URL encoded post data:
grant_type=authorization_code
client_id=[YOUR CLIENT ID]
client_secret=[YOUR CLIENT SECRET]
code= < enter the code from step 4 >
send the request and retrieve the resulting JSON data:
{
"access_token": "[YOUR SHINY NEW ACCESS TOKEN]",
"expires_in": 4255,
"restricted_to": [],
"refresh_token": "[YOUR HELPFUL REFRESH TOKEN]",
"token_type": "bearer"
}
In my application I save both auth token and refresh token in a format where I can easily go and replace them if something goes awry down the road. Then, I check my authentication each time I call into the API. If I get an authorization exception back I refresh my token programmatically, which you can do! Using the BoxApi.V2 .NET SDK this happens like so:
var authenticator = new TokenProvider(_clientId, _clientSecret);
// calling the 'RefreshAccessToken' method in the SDK
var newAuthToken = authenticator.RefreshAccessToken([YOUR EXISTING REFRESH TOKEN]);
// write the new token back to my data store.
Save(newAuthToken);
Hope this helped!
If I understand correctly you want the entire process to be automated so it would not require a user login (i.e run a script and the file is uploaded).
Well, it is possible. I am a rookie developer so excuse me if I'm not using the correct terms.
Anyway, this can be accomplished by using cURL.
First you need to define some variables, your user credentials (username and password), your client id and client secret given by Box (found in your app), your redirect URI and state (used for extra safety if I understand correctly).
The oAuth2.0 is a 4 step authentication process and you're going to need to go through each step individually.
The first step would be setting a curl instance:
curl_setopt_array($curl, array(
CURLOPT_URL => "https://app.box.com/api/oauth2/authorize",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "content-type: application/x-www-form-urlencoded",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS =>
"response_type=code&client_id=".$CLIENT_ID."&state=".$STATE,
));
This will return an html text with a request token, you will need it for the next step so I would save the entire output to a variable and grep the tag with the request token (the tag has a "name" = "request_token" and a "value" which is the actual token).
Next step you will need to send another curl request to the same url, this time the post fields should include the request token, user name and password as follows:
CURLOPT_POSTFIELDS => "response_type=code&client_id=".$CLIENT_ID."&state=".$STATE."&request_token=".$REQ_TOKEN."&login=".$USER_LOGIN."&password=".$PASSWORD
At this point you should also set a cookie file:
CURLOPT_COOKIEFILE => $COOKIE, (where $COOKIE is the path to the cookie file)
This will return another html text output, use the same method to grep the token which has the name "ic".
For the next step you're going to need to send a post request to the same url. It should include the postfields:
response_type=code&client_id=".$CLIENT_ID."&state=".$STATE."&redirect_uri=".$REDIRECT_URI."&doconsent=doconsent&scope=root_readwrite&ic=".$IC
Be sure to set the curl request to use the cookie file you set earlier like this:
CURLOPT_COOKIEFILE => $COOKIE,
and include the header in the request:
CURLOPT_HEADER => true,
At step (if done by browser) you will be redirected to a URL which looks as described above:
http://0.0.0.0(*redirect uri*)/?state=[box-generated_state_security_token]&code=[SOME_CODE] and note the "code=" value.
Grab the value of "code".
Final step!
send a new cur request to https//app.box.com/api/oauth2/token
This should include fields:
CURLOPT_POSTFIELDS => "grant_type=authorization_code&code=".$CODE."&client_id=".$CLIENT_ID."&client_secret=".$CLIENT_SECRET,
This will return a string containing "access token", "Expiration" and "Refresh token".
These are the tokens needed for the upload.
read about the use of them here:
https://box-content.readme.io/reference#upload-a-file
Hope this is somewhat helpful.
P.S,
I separated the https on purpuse (Stackoverflow wont let me post an answer with more than 1 url :D)
this is for PHP cURL. It is also possible to do the same using Bash cURL.
For anyone looking into this recently, the best way to do this is to create a Limited Access App in Box.
This will let you create an access token which you can use for server to server communication. It's simple to then upload a file (example in NodeJS):
import box from "box-node-sdk";
import fs from "fs";
(async function (){
const client = box.getBasicClient(YOUR_ACCESS_TOKEN);
await client.files.uploadFile(BOX_FOLDER_ID, FILE_NAME, fs.createReadStream(LOCAL_FILE_PATH));
})();
Have you thought about creating a box 'integration' user for this particular purpose. It seems like uploads have to be made with a Box account. It sounds like you are trying to do an anonymous upload. I think box, like most services, including stackoverflow don't want anonymous uploads.
You could create a system user. Go do the Oauth2 dance and store just the refresh token somewhere safe. Then as the first step of your script waking up go use the refresh token and store the new refresh token. Then upload all your files.

Resources