IdentityServer4 client - Refreshing access tokens on CookieAuthenticationEvents - authentication

I am trying to use refresh token when the access token expires. A similar so question is answered here. And a sample code to renew token by an action
And i end up with the following code in the startup.cs
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
//ExpireTimeSpan = TimeSpan.FromSeconds(100),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async x =>
{
if (x.Properties?.Items[".Token.expires_at"] == null) return;
var logger = loggerFactory.CreateLogger(this.GetType());
var now = DateTimeOffset.UtcNow;
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
if (timeElapsed > timeRemaining)
{
var httpContextAuthentication = x.HttpContext.Authentication;//Donot use the HttpContext.Authentication to retrieve anything, this cause recursive call to this event
var oldAccessToken = await httpContextAuthentication.GetTokenAsync("access_token");
var oldRefreshToken = await httpContextAuthentication.GetTokenAsync("refresh_token");
logger.LogInformation($"Refresh token :{oldRefreshToken}, old access token:{oldAccessToken}");
var disco = await DiscoveryClient.GetAsync(AuthorityServer);
if (disco.IsError) throw new Exception(disco.Error);
var tokenClient = new TokenClient(disco.TokenEndpoint, ApplicationId, "secret");
var tokenResult = await tokenClient.RequestRefreshTokenAsync(oldRefreshToken);
logger.LogInformation("Refresh token requested. " + tokenResult.ErrorDescription);
if (!tokenResult.IsError)
{
var oldIdToken = await httpContextAuthentication.GetTokenAsync("id_token");
var newAccessToken = tokenResult.AccessToken;
var newRefreshToken = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = oldIdToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.AccessToken, Value = newAccessToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.RefreshToken, Value = newRefreshToken}
};
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
var info = await httpContextAuthentication.GetAuthenticateInfoAsync("Cookies");
info.Properties.StoreTokens(tokens);
await httpContextAuthentication.SignInAsync("Cookies", info.Principal, info.Properties);
}
x.ShouldRenew = true;
}
else
{
logger.LogInformation("Not expired");
}
}
}
});
The client setup is as follows
AllowAccessTokensViaBrowser = true,
RefreshTokenUsage = TokenUsage.ReUse,
RefreshTokenExpiration = TokenExpiration.Sliding,
AbsoluteRefreshTokenLifetime = 86400,
AccessTokenLifetime = 10,
AllowOfflineAccess = true,
AccessTokenType = AccessTokenType.Reference
After successfully login, i am getting a 401 for every other request. And the log says
[Identity Server]2017-07-04 10:15:58.819 +01:00 [Debug]
"TjpIkvHQi../cfivu6Nql5ADJJlZRuoJV1QI=" found in database: True
[Identity Server]2017-07-04 10:15:58.820 +01:00 [Debug]
"reference_token" grant with value:
"..9e64c1235c6675fcef617914911846fecd72f7b372" found in store, but has
expired.
[Identity Server]2017-07-04 10:15:58.821 +01:00 [Error] Invalid
reference token. "{ \"ValidateLifetime\": true,
\"AccessTokenType\": \"Reference\", \"TokenHandle\":
\"..9e64c1235c6675fcef617914911846fecd72f7b372\" }"
[Identity Server]2017-07-04 10:15:58.822 +01:00 [Debug] Token is
invalid.
[Identity Server]2017-07-04 10:15:58.822 +01:00 [Debug] Creating
introspection response for inactive token.
[Identity Server]2017-07-04 10:15:58.822 +01:00 [Information] Success
token introspection. Token status: "inactive", for API name: "api1"
Any help would by highly appreciated
UPDATE:
Basically, when the token expires i get a System.StackOverflowException on the following line
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
UPDATE 2:
Do not use HttpContext.Authentication to retrieve anything. Check my answer below to find the working implementaion

I was working on this for last two days and could not make this work. Funnily, after posting the question here, within 2 hours I make it working :)
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async x =>
{
if (x.Properties?.Items[".Token.expires_at"] == null) return;
var now = DateTimeOffset.UtcNow;
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
WriteMessage($"{timeRemaining} and elapsed at {timeElapsed}");
if (timeElapsed > timeRemaining)
{
var oldAccessToken = x.Properties.Items[".Token.access_token"];
var oldRefreshToken = x.Properties.Items[".Token.refresh_token"];
WriteMessage($"Refresh token :{oldRefreshToken}, old access token {oldAccessToken}");
var disco = await DiscoveryClient.GetAsync(AuthorityServer);
if (disco.IsError) throw new Exception(disco.Error);
var tokenClient = new TokenClient(disco.TokenEndpoint, ApplicationId, "secret");
var tokenResult = await tokenClient.RequestRefreshTokenAsync(oldRefreshToken);
if (!tokenResult.IsError)
{
var oldIdToken = x.Properties.Items[".Token.id_token"];//tokenResult.IdentityToken
var newAccessToken = tokenResult.AccessToken;
var newRefreshToken = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = oldIdToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.AccessToken, Value = newAccessToken},
new AuthenticationToken {Name = OpenIdConnectParameterNames.RefreshToken, Value = newRefreshToken}
};
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
x.Properties.StoreTokens(tokens);
WriteMessage($"oldAccessToken: {oldAccessToken}{Environment.NewLine} and new access token {newAccessToken}");
}
x.ShouldRenew = true;
}
}
}
Basically httpContextAuthentication.GetTokenAsync make this recursive, for that reason StackOverflowException occured.
Please let me know if this implementation has any issue

Related

FTX API in Google Apps Script not authenticating

I've successfully connected to most other crypto exchanges but keep getting "success":false,"error":"Not logged in" when using FTX.
This is my code:
var host = 'https://ftx.com/api';
var endpoint ='/wallet/all_balances';
var url = host + endpoint;
var timestamp = ''+ new Date().getTime();
var payload = timestamp + 'GET' + endpoint+'';
var shaObj = new jsSHA("SHA-256", "BYTES");
shaObj.setHMACKey(secret, "BYTES");
shaObj.update(payload);
var signature = shaObj.getHMAC("HEX");
var options = {
method: 'get',
headers: {
'FTX-KEY': key,
'FTX-TS': timestamp,
'FTX-SIGN': signature
},
muteHTTPExceptions: 'true'
}
var jsondata = UrlFetchApp.fetch(url, options);
var data = JSON.parse(jsondata.getContentText());
Any help much appreciated!
Found the answer:
var host = 'https://ftx.com';
var endpoint ='/api/wallet/all_balances';

Pagination for Google Apps Script with Shopify API

I am running into a bit of a snag setting up pagination in Google Apps Script. I am trying to use it for Shopify API. Reference links attached.
I attached the code below of what I have so far -
trying to figure out how to use the "While" statement to make it check if there is a Next Page URL
trying to figure out a way to parse the Link in the header. Example below. On pages 2+ there will be a next and previous link. We only need the next
https://shop-domain.myshopify.com/admin/api/2019-07/products.json?limit=50&page_info=eyJkaXJlY3Rpb24iOiJwcmV2IiwibGFzdF9pZCI6MTk4NTgyMTYzNCwibGFzdF92YWx1ZSI6IkFjcm9saXRoaWMgQWx1bWludW0gUGVuY2lsIn0%3D; rel="previous", https://shop-domain.myshopify.com/admin/api/2019-07/products.json?limit=50&page_info=eyJkaXJlY3Rpb24iOiJuZXh0IiwibGFzdF9pZCI6MTk4NTgzNjU0NiwibGFzdF92YWx1ZSI6IkFoaXN0b3JpY2FsIFZpbnlsIEtleWJvYXJkIn0%3D; rel="next
function callShopifyOrderCount() {
var accessToken = 'xxxx';
var store_url = 'https://xxxx.myshopify.com/admin/api/2021-01/orders.json?status=any&fields=created_at,id,name,total-price&limit=20';
var headers = {
"Content-Type": "application/json",
'X-Shopify-Access-Token': accessToken
};
var options = {
"method": "GET",
"headers": headers,
"contentType": "application/json",
"muteHttpExceptions": true,
}
var response = UrlFetchApp.fetch(store_url, options)
// Call the link header for next page
var header = response.getHeaders()
var linkHeader = header.Link;
var responseCode = response.getResponseCode();
var responseBody = response.getContentText();
if (responseCode === 200) {
var responseJson = JSON.parse(responseBody);
var objectLength = responseJson.orders.length;
for (var i = 0; i < objectLength; i++) {
var orderId = responseJson.orders[i].id;
var orderPrice = responseJson.orders[i].total_price;
var orderName = responseJson.orders[i].name;
}
} else {
Logger.log(
Utilities.formatString(
"First Request failed. Expected 200, got %d: %s",
responseCode,
responseBody
)
);
// ...
}
// *** NEED TO FIGURE OUT WHILE STATEMENT //
while (Link != null) {
var store_url = linkHeader;
var response = UrlFetchApp.fetch(store_url, options)
var responseCode = response.getResponseCode();
var responseBody = response.getContentText();
var header = response.getHeaders()
var linkHeader = header.Link;
if (responseCode === 200) {
var responseJson = JSON.parse(responseBody);
var objectLength = responseJson.orders.length;
for (var i = 0; i < tweetLength; i++) {
var orderId = responseJson.orders[i].id;
var orderPrice = responseJson.orders[i].total_price;
var orderName = responseJson.orders[i].name;
}
}
else {
Logger.log(
Utilities.formatString(
"Second Request failed. Expected 200, got %d: %s",
responseCode,
responseBody
)
);
}
}
}
References:
https://shopify.dev/tutorials/make-paginated-requests-to-rest-admin-api
https://www.shopify.com/partners/blog/relative-pagination

Flutter : unhandled exception: invalid argument

I'm trying to send two list of images file and i have an error with that and i don't know why this happen
this is the function which i use for that :
Future<dynamic> upload(List<File> imageFile, List<File> backID) async {
var headers =
Provider.of<LoginUserProvider>(context, listen: false).httpHeader;
var uri = Uri.parse(
"xyz");
var request = new http.MultipartRequest("POST", uri);
var requestCopy = new http.MultipartRequest("POST", uri);
request.headers.addAll(headers);
//request.headers.addAll(Environment.requestHeaderMedia);
for (int i = 0; i < uploadedFilesList.length; i++) {
var length = await imageFile[i].length();
var stream =
// ignore: deprecated_member_use
new http.ByteStream(DelegatingStream.typed(imageFile[i].openRead()));
var multipartFile = http.MultipartFile(
'back_id_photos[$i]',
stream,
length,
contentType: MediaType('application', 'x-tar'),
);
var multipartFilePhotos = new http.MultipartFile(
'photos[$i]',
stream,
length,
contentType: MediaType('application', 'x-tar'),
);
request.fields['section_id'] = VillaID.toString();
request.fields['start_date'] = requeststartDate;
request.fields['end_date'] = requestendDate;
request.fields['names'] = _nameController.toString();
request.fields['phones'] = _phoneController.toString();
request.fields['photos'] = multipartFilePhotos.toString();
request.fields['back_id_photos'] = multipartFile.toString();
//contentType: new MediaType('image', 'png'));
request.files.add(multipartFile);
request.files.add(multipartFilePhotos);
var response = await request.send();
print(response.statusCode);
response.stream.transform(utf8.decoder).listen((value) {
print(value);
});
var streamedResponse = await requestCopy.send();
var responses = await http.Response.fromStream(streamedResponse);
// ignore: unused_local_variable
final responseData = json.decode(responses.body) as Map<String, dynamic>;
print(json.decode(responses.body));
if (response.statusCode == 200 || response.statusCode == 201) {
return true;
}
}
}
and in the part of
var response = await request.send();
I have the error exactly before the dot (.) and this is the error which shown
unhandled exception: invalid argument(s) (input): must not be null
Can anyone tell me what should i do to solve that !
Send your Multipart Part Files as below and Use await http.Multipart as accessing file is async function and need a promise to run.
Try this:
await http.MultipartFile(
'back_id_photos[$i]',
stream,
length,
contentType: MediaType('application', 'x-tar'),
);
OR
request.files.add(new http.MultipartFile.fromBytes('file', await File.fromUri("<path/to/file>").readAsBytes(), contentType: new MediaType('image', 'jpeg')))
Follow this link

Missing user_metadata in userInfo of auth0

For authentication I am using Auth0 AuthenticationApi. In Account Controller, I need to fetch the user_metadata but it's missing. Any alternative to fetch the user_metadata?
AuthenticationApiClient client = new AuthenticationApiClient(new Uri($"https://{_auth0Options.Domain}/"));
var authenticateResponse = await client.GetTokenAsync(new ResourceOwnerTokenRequest
{
ClientId = _auth0Options.ClientId,
ClientSecret = _auth0Options.ClientSecret,
Scope = "openid",
Realm = _auth0Options.Connection,
Username = vm.EmailAddress,
Password = vm.Password
});
var user = await client.GetUserInfoAsync(authenticateResponse.AccessToken);
if (user.UserMetadata != null)
{
// Giving error...any alternative to access the userMetaData ?
}
Yes, as far as I see it now, the legacy call still works. However, I don't have a non-legacy solution yet :(
using (var client = GetClient())
{
var jObject = new JObject(new JProperty("id_token", id_token));
var response = await client.PostAsJsonAsync("tokeninfo", jObject);
if (response.IsSuccessStatusCode)
{
var userProfileJson = JObject.Parse(await response.Content.ReadAsStringAsync());
retVal.user_id = userProfileJson.Value<string>("user_id");
retVal.email = userProfileJson.Value<string>("email");
retVal.user_name = userProfileJson.Value<string>("nickname");
if (userProfileJson.Value<string>("created_at") != null)
{
retVal.created_at = userProfileJson.Value<DateTime>("created_at");
}
var exists = userProfileJson.TryGetValue("user_metadata", out JToken meta);

Update claims after login with identityserver3 2.1.1

We need to update users claims after they log in to our website. This is caused by changes in the users licenses done by another part of our system.
However I am not able to comprehend how to update the claims without logout/login.
Rigth now this is our client setup
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
//user validation host
Authority = UrlConstants.BaseAddress,
//Client that the user is validating against
ClientId = guid,//if not convertet to Gui the compare from the server fails
RedirectUri = UrlConstants.RedirectUrl,
PostLogoutRedirectUri = UrlConstants.RedirectUrl,
ResponseType = "code id_token token",
Scope = "openid profile email roles licens umbraco_api umbracoaccess",
UseTokenLifetime = false,
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
_logger.Info("ConfigureAuth", "Token valdidated");
var id = n.AuthenticationTicket.Identity;
var nid = new ClaimsIdentity(
id.AuthenticationType,
Constants.ClaimTypes.GivenName,
Constants.ClaimTypes.Role);
// get userinfo data
var uri = new Uri(n.Options.Authority + "/connect/userinfo");
var userInfoClient = new UserInfoClient(uri,n.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
userInfo.Claims.ToList().ForEach(ui => nid.AddClaim(new Claim(ui.Item1, ui.Item2)));
var licens = id.FindAll(LicenseScope.Licens);
nid.AddClaims(licens);
// keep the id_token for logout
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
_logger.Info("ConfigureAuth", "AuthenticationTicket created");
},
RedirectToIdentityProvider = async n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token").Value;
_logger.Debug("ConfigureAuth", "id_token for logout set on request");
_logger.Debug("ConfigureAuth", "Old PostLogoutRedirectUri: {0}", n.ProtocolMessage.PostLogoutRedirectUri.ToString());
n.ProtocolMessage.IdTokenHint = idTokenHint;
var urlReferrer = HttpContext.Current.Request.UrlReferrer.ToString();
if (!urlReferrer.Contains("localhost"))
{
n.ProtocolMessage.PostLogoutRedirectUri = GetRedirectUrl();
}
else
{
n.ProtocolMessage.PostLogoutRedirectUri = urlReferrer;
}
_logger.Debug("ConfigureAuth", string.Format("Setting PostLogoutRedirectUri to: {0}", n.ProtocolMessage.PostLogoutRedirectUri.ToString()));
}
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.AuthenticationRequest)
{
n.ProtocolMessage.RedirectUri = GetRedirectUrl2();
n.ProtocolMessage.AcrValues = GetCurrentUmbracoId();
_logger.Debug("ConfigureAuth", string.Format("Setting RedirectUri to: {0}", n.ProtocolMessage.RedirectUri.ToString()));
}
},
}
});
We get our custom claims in SecurityTokenValidated
var licens = id.FindAll(LicenseScope.Licens);
nid.AddClaims(licens);
I do not follow how to get this without doing a login? Any help is highly appreciated.
That's a reminder that you should not put claims into tokens that might change during the lifetime of the session.
That said - you can set a new cookie at any point in time.
Reach into the OWIN authentication manager and call the SignIn method. Pass the claims identity that you want to serialize into the cookie.
e.g.
Request.GetOwinContext().Authentication.SignIn(newIdentity);