Using a HTTP Proxy Integration I want to access the cookies and add one to the json response. Is that possible?
To access cookies sent by the client in your backend you'll have to setup a mapping from the method request header to your integration request header.
These instructions assume you've already setup a simple method in API Gateway.
Access cookies in your backend
Under Method Request, create an HTTP Request Header with the name of "Cookie"
Under Integration Request, create an HTTP header with name "Cookie" and "Mapped from" value of method.request.header.Cookie.
You'll also likely need to setup CORS for this method. See: http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html
Deploy your API and make a request to your API Gateway endpoint with your browser/client. You should see requests coming in to your HTTP backend with the Cookie header value sent from the browser.
Add cookie to response
You can setup a Set-Cookie response header in an analogous fashion for the the integration response/method response side of the method configuration.
Under Method Response, create a Response header with name Set-Cookie
Under Integration Response setup a Header Mapping with Response header Set-Cookie and Mapping value integration.response.header.Set-Cookie
Please note that at this time, API Gateway supports setting just a single Set-Cookie response header. If your backend attempts to set multiple Set-Cookie headers, only the last one will be set. See this forum post for more details: https://forums.aws.amazon.com/thread.jspa?messageID=701434
If you check the "Use Lambda Proxy integration" option in your API Gateway method, the request headers will be passed to your Lambda function via the event variable. API Gateway will also expect a different response from your callback function. This response format can be use to dictate a Set-Cookie header. e.g.:
callback(null, {
statusCode: 200,
headers: {'Set-Cookie': 'key=val'},
body: 'Some response'
})`
This approach has the advantage of not requiring any Method Request or Method Response tweaks.
Here's a sample Lambda function using this logic to rotate a cookie value after each request.
exports.handler = (event, context, callback) => {
var cookies = getCookiesFromHeader(event.headers);
var old_cookie = cookies.flavor;
var new_cookie = pickCookieFlavor(old_cookie);
return callback(null, {
statusCode: 200,
headers: {
'Set-Cookie': setCookieString('flavor', new_cookie),
'Content-Type': 'text/plain'
},
body: 'Your cookie flavor was ' + old_cookie + '. Your new flavor is ' + new_cookie + '.'
});
};
/**
* Rotate the cookie flavor
*/
function pickCookieFlavor(cookie) {
switch (cookie) {
case 'peanut':
return 'chocolate';
case 'chocolate':
return 'raisin and oat';
default:
return 'peanut';
}
}
/**
* Receives an array of headers and extract the value from the cookie header
* #param {String} errors List of errors
* #return {Object}
*/
function getCookiesFromHeader(headers) {
if (headers === null || headers === undefined || headers.Cookie === undefined) {
return {};
}
// Split a cookie string in an array (Originally found http://stackoverflow.com/a/3409200/1427439)
var list = {},
rc = headers.Cookie;
rc && rc.split(';').forEach(function( cookie ) {
var parts = cookie.split('=');
var key = parts.shift().trim()
var value = decodeURI(parts.join('='));
if (key != '') {
list[key] = value
}
});
return list;
};
/**
* Build a string appropriate for a `Set-Cookie` header.
* #param {string} key Key-name for the cookie.
* #param {string} value Value to assign to the cookie.
* #param {object} options Optional parameter that can be use to define additional option for the cookie.
* ```
* {
* secure: boolean // Watever to output the secure flag. Defaults to true.
* httpOnly: boolean // Watever to ouput the HttpOnly flag. Defaults to true.
* domain: string // Domain to which the limit the cookie. Default to not being outputted.
* path: string // Path to which to limit the cookie. Defaults to '/'
* expires: UTC string or Date // When this cookie should expire. Default to not being outputted.
* maxAge: integer // Max age of the cookie in seconds. For compatibility with IE, this will be converted to a
* `expires` flag. If both the expires and maxAge flags are set, maxAge will be ignores. Default to not being
* outputted.
* }
* ```
* #return string
*/
function setCookieString(key, value, options) {
var defaults = {
secure: true,
httpOnly: true,
domain: false,
path: '/',
expires: false,
maxAge: false
}
if (typeof options == 'object') {
options = Object.assign({}, defaults, options);
} else {
options = defaults;
}
var cookie = key + '=' + value;
if (options.domain) {
cookie = cookie + '; domain=' + options.domain;
}
if (options.path) {
cookie = cookie + '; path=' + options.path;
}
if (!options.expires && options.maxAge) {
options.expires = new Date(new Date().getTime() + parseInt(options.maxAge) * 1000); // JS operate in Milli-seconds
}
if (typeof options.expires == "object" && typeof options.expires.toUTCString) {
options.expires = options.expires.toUTCString();
}
if (options.expires) {
cookie = cookie + '; expires=' + options.expires.toString();
}
if (options.secure) {
cookie = cookie + '; Secure';
}
if (options.httpOnly) {
cookie = cookie + '; HttpOnly';
}
return cookie;
}
Related
In AWS I am configuring CloudFront with S3 Origin. The S3 is configured for Server-Side Encryption with Customer Master Keys (CMKs). Because of the coudfront issue with S3 with CMK encryption, we have to use Lambda#Edge function as suggested in this AWS Article
The Node JS code below is copied from the article linked
// Declare constants reqiured for the signature process
const crypto = require('crypto');
const emptyHash = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
const signedHeadersGeneric = 'host;x-amz-content-sha256;x-amz-date;x-amz-security-token';
// CloudFront includes the x-amz-cf-id header in the signature for custom origins
const signedHeadersCustomOrigin = 'host;x-amz-cf-id;x-amz-content-sha256;x-amz-date;x-amz-security-token';
// Retrieve the temporary IAM credentials of the function that were granted by
// the Lambda#Edge service based on the function permissions. In this solution, the function
// is given permissions to read from S3 and decrypt using the KMS key.
const { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN } = process.env;
// Since the function is configured to be executed on origin request events, the handler
// is executed every time CloudFront needs to go back to the origin, which is S3 here.
exports.handler = async event => {
// Retrieve the original request that CloudFront was going to send to S3
const request = event.Records[0].cf.request;
// The request object has different properties depending on the type of
// origin that is being used. Account for that here.
let originType = '';
if (request.origin.hasOwnProperty('s3'))
originType = 's3';
else if (request.origin.hasOwnProperty('custom'))
originType = 'custom';
else
throw("Unexpected origin type. Expected 's3' or 'custom'. Got: " + JSON.stringify(request.origin));
// Create a JSON object with the fields that should be included in the Sigv4 request,
// including the X-Amz-Cf-Id header that CloudFront adds to every request forwarded
// upstream. This header is exposed to Lambda#Edge in the event object
const sigv4Options = {
method: request.method,
path: request.origin[originType].path + request.uri,
credentials: {
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY,
sessionToken: AWS_SESSION_TOKEN
},
host: request.headers['host'][0].value,
xAmzCfId: event.Records[0].cf.config.requestId,
originType: originType
};
// Compute the signature object that includes the following headers: X-Amz-Security-Token, Authorization,
// X-Amz-Date, X-Amz-Content-Sha256, and X-Amz-Security-Token
const signature = signV4(sigv4Options);
// Finally, add the signature headers to the request before it is sent to S3
for(var header in signature){
request.headers[header.toLowerCase()] = [{
key: header,
value: signature[header].toString()
}];
}
return request;
};
// Helper functions to sign the request using AWS Signature Version 4
// This helper only works for S3, using GET/HEAD requests, without query strings
// https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
function signV4(options) {
// Infer the region from the host header
const region = options.host.split('.')[2];
// Create the canonical request
const date = (new Date()).toISOString().replace(/[:-]|\.\d{3}/g, '');
let canonicalHeaders = '';
let signedHeaders = '';
if (options.originType == 's3') {
canonicalHeaders = ['host:'+options.host, 'x-amz-content-sha256:'+emptyHash, 'x-amz-date:'+date, 'x-amz-security-token:'+options.credentials.sessionToken].join('\n');
signedHeaders = signedHeadersGeneric;
} else {
canonicalHeaders = ['host:'+options.host, 'x-amz-cf-id:'+options.xAmzCfId, 'x-amz-content-sha256:'+emptyHash, 'x-amz-date:'+date, 'x-amz-security-token:'+options.credentials.sessionToken].join('\n');
signedHeaders = signedHeadersCustomOrigin;
}
const canonicalURI = encodeRfc3986(encodeURIComponent(decodeURIComponent(options.path).replace(/\+/g, ' ')).replace(/%2F/g, '/'));
const canonicalRequest = [options.method, canonicalURI, '', canonicalHeaders + '\n', signedHeaders,emptyHash].join('\n');
// Create string to sign
const credentialScope = [date.slice(0, 8), region, 's3/aws4_request'].join('/');
const stringToSign = ['AWS4-HMAC-SHA256', date, credentialScope, hash(canonicalRequest, 'hex')].join('\n');
// Calculate the signature
const signature = hmac(hmac(hmac(hmac(hmac('AWS4' + options.credentials.secretAccessKey, date.slice(0, 8)), region), "s3"), 'aws4_request'), stringToSign, 'hex');
// Form the authorization header
const authorizationHeader = ['AWS4-HMAC-SHA256 Credential=' + options.credentials.accessKeyId + '/' + credentialScope,'SignedHeaders=' + signedHeaders,'Signature=' + signature].join(', ');
// return required headers for Sigv4 to be added to the request to S3
return {
'Authorization': authorizationHeader,
'X-Amz-Content-Sha256' : emptyHash,
'X-Amz-Date': date,
'X-Amz-Security-Token': options.credentials.sessionToken
};
}
function encodeRfc3986(urlEncodedStr) {
return urlEncodedStr.replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase())
}
function hash(string, encoding) {
return crypto.createHash('sha256').update(string, 'utf8').digest(encoding)
}
function hmac(key, string, encoding) {
return crypto.createHmac('sha256', key).update(string, 'utf8').digest(encoding)
}
However when I try to deploy the lambda#edge function with the suggested Node JS code it throws error Cannot read properties of undefined (reading 'startsWith')
What could be the issue here
Select in dropdown "viewer response" or "origin response" and after that return to "origin request".
I am using initial Nuxt function, that is invoked on reload, to set important data like userId and token. I can read data from cookie, but I can't save data to cookie.
initAuth(context, req) {
try {
const cookie = req.headers.cookie
if (req && cookie) {
let token = getCookieServerSide('token', cookie)
let userId = getCookieServerSide('userId', cookie)
let deviceId = getCookieServerSide('deviceId', cookie)
if (token) {
context.commit('SET_TOKEN', { token })
}
if (userId) {
userId = parseInt(userId)
context.commit('users/SET_USER_ID', userId, { root: true })
}
if (!deviceId) {
deviceId = generateUniqueId()
setCookie('deviceId', deviceId)
}
context.commit('SET_DEVICE_ID', deviceId)
}
} catch (error) {}
This is initial method, and setCookie looks like this:
export const setCookie = (name, value) => {
Cookie.set(name, value)
}
By reading different comments this should be valid solution, but cookie isn't saved
Your current cookie lib is only compatible with an execution on the front-end code.
To set cookie on both client and server sides, you have to use a lib that supports an universal usage, like universal-cookie.
With Nuxt, you can use cookie-universal-nuxt to set, get and remove cookies in both client and server side, based on the previous lib.
Scenario:
I query an HTTP POST (using Authorizer as Header parameter from Cognito).
When I try to fetch/read the query response, it triggers the error event. However, in the browser, I can see how 2 HTTP POST responses with 200 code and one of them returning the valid response. For example: if I make the request via POST man I receive the data in 1 response in a good way.
Problem:
I am unable to print the result because it launches the error event with not valid response data.
Browser images:
https://i.postimg.cc/MTMsxZjw/Screenshot-1.png
https://i.postimg.cc/3RstwMgv/Screenshot-2.png
Lambda code:
'use strict';
var AWS = require('aws-sdk'),
documentClient = new AWS.DynamoDB.DocumentClient();
exports.handler = function index(event, context, callback){
var params = {
TableName : "data-table"
};
documentClient.scan(params, function(err, data){
if(err){
callback(err, null);
}else{
console.log(JSON.stringify(data.Items));
callback(null, data.Items);
}
});
}
Client side JS code:
function requestData(pickupLocation) {
$.ajax({
type: 'POST',
url: _config.api.invokeUrl,
headers: {
Authorization: authToken,
},
data: "{}",
cache: false,
success: completeRequest,
error: errorRequest
});
}
function completeRequest(response) {
alert("hello");
alert(response.d);
}
function errorRequest(response) {
alert("hello1");
alert(response.status + ' ' + response.statusText);
}
According to further clarification based on the comments, this looks like API gateway has CORS disabled or enabled with incorrect header value returns.
The solution is to re-enable CORS through API gateway and in the advanced options add Access-Control-Allow-Origin to the header response (if not already on by default).
If you're proxying the response, you need to follow a specific format as described here
'use strict';
console.log('Loading hello world function');
exports.handler = async (event) => {
let name = "you";
let city = 'World';
let time = 'day';
let day = '';
let responseCode = 200;
console.log("request: " + JSON.stringify(event));
// This is a simple illustration of app-specific logic to return the response.
// Although only 'event.queryStringParameters' are used here, other request data,
// such as 'event.headers', 'event.pathParameters', 'event.body', 'event.stageVariables',
// and 'event.requestContext' can be used to determine what response to return.
//
if (event.queryStringParameters && event.queryStringParameters.name) {
console.log("Received name: " + event.queryStringParameters.name);
name = event.queryStringParameters.name;
}
if (event.pathParameters && event.pathParameters.proxy) {
console.log("Received proxy: " + event.pathParameters.proxy);
city = event.pathParameters.proxy;
}
if (event.headers && event.headers['day']) {
console.log("Received day: " + event.headers.day);
day = event.headers.day;
}
if (event.body) {
let body = JSON.parse(event.body)
if (body.time)
time = body.time;
}
let greeting = `Good ${time}, ${name} of ${city}. `;
if (day) greeting += `Happy ${day}!`;
let responseBody = {
message: greeting,
input: event
};
// The output from a Lambda proxy integration must be
// of the following JSON object. The 'headers' property
// is for custom response headers in addition to standard
// ones. The 'body' property must be a JSON string. For
// base64-encoded payload, you must also set the 'isBase64Encoded'
// property to 'true'.
let response = {
statusCode: responseCode,
headers: {
"x-custom-header" : "my custom header value"
},
body: JSON.stringify(responseBody)
};
console.log("response: " + JSON.stringify(response))
return response;
};
If you are using chrome you probably need the cors plugin .
My objective is to protect an aws s3 bucket link and I'm trying to solve this by using cloudfront as the link via which the s3 buckets are accessible, hence when a user tries to access the cloudfront link, there is a basic auth if there's no cookie in their browser, but if there's a cookie, then auth values in this cookie is checked and user is granted access.
PS: This is not a website, my quest is to protect s3 bucket links.
Here is my attempt, using lambda#edge, on viewer request, there's the auth page if user is not logged in, otherwise, they're allowed access, it works but I can't set cookies, because somewhere in aws documentation, cloudfront deletes set-cookies in header files: CloudFront removes the Cookie header from requests that it forwards to your origin and removes the Set-Cookie header from responses that it returns to your viewers
Here is my code:
'use strict';
// returns a response error
const responseError = {
status: '401',
statusDescription: 'Unauthorized',
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
}
};
exports.handler = (event, context, callback) => {
// Get request and request headers
console.log(event.Records[0]);
const request = event.Records[0].cf.request;
const response = event.Records[0].cf.response;
const headers = request.headers;
// checks to see if headers exists with cookies
let hasTheHeader = (request, headerKey) => {
if (request.headers[headerKey]) {
return true;
}
else return false;
};
// Add set-cookie header to origin response
const setCookie = function(response, cookie) {
const cookieValue = `${cookie}`;
console.log(`Setting cookie ${cookieValue}`);
response.headers['set-cookie'] = [{ key: "Set-Cookie", value: cookieValue }];
}
// Configure authentication
const authUser = 'someuser';
const authPass = 'testpassword';
let authToken;
let authString;
// Construct the Auth string
const buff = new Buffer(authUser + ':' + authPass).toString('base64');
authString = 'Basic ' + buff;
const authCookie = 'testAuthToken';
//execute this on viewer request that is if request type is viewer request:
if(event.Records[0].cf.config.eventType == 'viewer-request'){
//check if cookies exists and assign authToken if it does not
if(hasTheHeader(request, 'cookie') ){
for (let i = 0; i < headers.cookie.length; i++)
{
if (headers.cookie[i].value.indexOf(authString) >= 0)
{
authToken = authString;
console.log(authToken);
break;
}
}
}
if (!authToken)
{
if (headers && headers.authorization && headers.authorization[0].value === authString)
{
// Set-Cookie: testAuthToken= new Buffer(authUser + ':' + authPass).toString('base64')
authToken = authString;
request.header.cookie = [];
//put cookie value to custom header - format is important
request.headers.cookie.push({'key': 'Cookie', 'value': authString});
}
else
{
callback(null, responseError);
}
// continue forwarding request
callback(null, request);
}
else{
//strip out "Basic " to extract Basic credential in base 64
var authInfo = authToken.slice(6);
var userCredentials = new Buffer(authInfo, 'base64');
var userLoginNamePass = userCredentials.toString();
var baseCredentials = userLoginNamePass.split(":");
var username = baseCredentials[0];
var userPass = baseCredentials[1];
if (username != authUser && userPass != authPass) {
//user auth failed
callback(null, responseError);
} else {
request.header.cookie = [];
//put cookie value to custom header - format is important
request.headers.cookie.push({'key': 'Cookie', 'value': authString});
}
// continue forwarding request
callback(null, request);
}
}
else if(event.Records[0].cf.config.eventType == 'origin-response')
{
if(hasTheHeader(request, 'cookie')){
for (let i = 0; i < headers.cookie.length; i++)
{
if (headers.cookie[i].value.indexOf(authString) >= 0)
{
setCookie(response, authString);
break;
}
}
}
// console.log(res_headers);
console.log("response: " + JSON.stringify(response));
callback(null, response);
}
};
Your suggestions will be most welcome. Thanks in advance.
I have configured an ASOS OpenIdConnect Server using and an asp.net core mvc app that uses the "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0 and "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0". I have tested the "Authorization Code" workflow and everything works.
The client web app processes the authentication as expected and creates a cookie storing the id_token, access_token, and refresh_token.
How do I force Microsoft.AspNetCore.Authentication.OpenIdConnect to request a new access_token when it expires?
The asp.net core mvc app ignores the expired access_token.
I would like to have openidconnect see the expired access_token then make a call using the refresh token to get a new access_token. It should also update the cookie values. If the refresh token request fails I would expect openidconnect to "sign out" the cookie (remove it or something).
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = "myClient",
ClientSecret = "secret_secret_secret",
PostLogoutRedirectUri = "http://localhost:27933/",
RequireHttpsMetadata = false,
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true,
ResponseType = OpenIdConnectResponseType.Code,
AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet,
Authority = http://localhost:27933,
MetadataAddress = "http://localhost:27933/connect/config",
Scope = { "email", "roles", "offline_access" },
});
It seems there is no programming in the openidconnect authentication for asp.net core to manage the access_token on the server after received.
I found that I can intercept the cookie validation event and check if the access token has expired. If so, make a manual HTTP call to the token endpoint with the grant_type=refresh_token.
By calling context.ShouldRenew = true; this will cause the cookie to be updated and sent back to the client in the response.
I have provided the basis of what I have done and will work to update this answer once all work as been resolved.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookies",
ExpireTimeSpan = new TimeSpan(0, 0, 20),
SlidingExpiration = false,
CookieName = "WebAuth",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = context =>
{
if (context.Properties.Items.ContainsKey(".Token.expires_at"))
{
var expire = DateTime.Parse(context.Properties.Items[".Token.expires_at"]);
if (expire > DateTime.Now) //TODO:change to check expires in next 5 mintues.
{
logger.Warn($"Access token has expired, user: {context.HttpContext.User.Identity.Name}");
//TODO: send refresh token to ASOS. Update tokens in context.Properties.Items
//context.Properties.Items["Token.access_token"] = newToken;
context.ShouldRenew = true;
}
}
return Task.FromResult(0);
}
}
});
You must enable the generation of refresh_token by setting in startup.cs:
Setting values to AuthorizationEndpointPath = "/connect/authorize"; // needed for refreshtoken
Setting values to TokenEndpointPath = "/connect/token"; // standard token endpoint name
In your token provider, before validating the token request at the end of the HandleTokenrequest method, make sure you have set the offline scope:
// Call SetScopes with the list of scopes you want to grant
// (specify offline_access to issue a refresh token).
ticket.SetScopes(
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess);
If that is setup properly, you should receive a refresh_token back when you login with a password grant_type.
Then from your client you must issue the following request (I'm using Aurelia):
refreshToken() {
let baseUrl = yourbaseUrl;
let data = "client_id=" + this.appState.clientId
+ "&grant_type=refresh_token"
+ "&refresh_token=myRefreshToken";
return this.http.fetch(baseUrl + 'connect/token', {
method: 'post',
body : data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
});
}
and that's it, make sure that your auth provider in HandleRequestToken is not trying to manipulate the request that is of type refresh_token:
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsPasswordGrantType())
{
// Password type request processing only
// code that shall not touch any refresh_token request
}
else if(!context.Request.IsRefreshTokenGrantType())
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid grant type.");
return;
}
return;
}
The refresh_token shall just be able to pass through this method and is handled by another piece of middleware that handles refresh_token.
If you want more in depth knowledge about what the auth server is doing, you can have a look at the code of the OpenIdConnectServerHandler:
https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/master/src/AspNet.Security.OpenIdConnect.Server/OpenIdConnectServerHandler.Exchange.cs
On the client side you must also be able to handle the auto refresh of the token, here is an example of an http interceptor for Angular 1.X, where one handles 401 reponses, refresh the token, then retry the request:
'use strict';
app.factory('authInterceptorService',
['$q', '$injector', '$location', 'localStorageService',
function ($q, $injector, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var $http;
var _request = function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
};
var _responseError = function (rejection) {
var deferred = $q.defer();
if (rejection.status === 401) {
var authService = $injector.get('authService');
console.log("calling authService.refreshToken()");
authService.refreshToken().then(function (response) {
console.log("token refreshed, retrying to connect");
_retryHttpRequest(rejection.config, deferred);
}, function () {
console.log("that didn't work, logging out.");
authService.logOut();
$location.path('/login');
deferred.reject(rejection);
});
} else {
deferred.reject(rejection);
}
return deferred.promise;
};
var _retryHttpRequest = function (config, deferred) {
console.log('autorefresh');
$http = $http || $injector.get('$http');
$http(config).then(function (response) {
deferred.resolve(response);
},
function (response) {
deferred.reject(response);
});
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
authInterceptorServiceFactory.retryHttpRequest = _retryHttpRequest;
return authInterceptorServiceFactory;
}]);
And here is an example I just did for Aurelia, this time I wrapped my http client into an http handler that checks if the token is expired or not. If it is expired it will first refresh the token, then perform the request. It uses a promise to keep the interface with the client-side data services consistent. This handler exposes the same interface as the aurelia-fetch client.
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
import {AuthService} from './authService';
#inject(HttpClient, AuthService)
export class HttpHandler {
constructor(httpClient, authService) {
this.http = httpClient;
this.authService = authService;
}
fetch(url, options){
let _this = this;
if(this.authService.tokenExpired()){
console.log("token expired");
return new Promise(
function(resolve, reject) {
console.log("refreshing");
_this.authService.refreshToken()
.then(
function (response) {
console.log("token refreshed");
_this.http.fetch(url, options).then(
function (success) {
console.log("call success", url);
resolve(success);
},
function (error) {
console.log("call failed", url);
reject(error);
});
}, function (error) {
console.log("token refresh failed");
reject(error);
});
}
);
}
else {
// token is not expired, we return the promise from the fetch client
return this.http.fetch(url, options);
}
}
}
For jquery you can look a jquery oAuth:
https://github.com/esbenp/jquery-oauth
Hope this helps.
Following on from #longday's answer, I have had success in using this code to force a client refresh without having to manually query an open id endpoint:
OnValidatePrincipal = context =>
{
if (context.Properties.Items.ContainsKey(".Token.expires_at"))
{
var expire = DateTime.Parse(context.Properties.Items[".Token.expires_at"]);
if (expire > DateTime.Now) //TODO:change to check expires in next 5 mintues.
{
context.ShouldRenew = true;
context.RejectPrincipal();
}
}
return Task.FromResult(0);
}