we have asp.net mvc 5 web app , use angularjs data mvc controllers (not apicontrollers). authentication linked azure ad using cookie authentication default expiry of after 1 hour.
the app spa. once user logs in not navigate other pages use ajax ($http) calls only.
so far, extended redirecttoidentityprovider method in startup.configuration() recognize ajax calls , return error 403 client-side when token expired. way, avoid redirecting authority page , cors error.
further, implemented persistent token cache helper tokencache
(namespace microsoft.identitymodel.clients.activedirectory
) in authorizationcodereceived
of same class.
app.setdefaultsigninasauthenticationtype(cookieauthenticationdefaults.authenticationtype); app.usecookieauthentication(new cookieauthenticationoptions()); app.useopenidconnectauthentication( new openidconnectauthenticationoptions { clientid = configurationhelper.clientid, authority = configurationhelper.azureadauthorizationuri, tokenvalidationparameters = new system.identitymodel.tokens.tokenvalidationparameters { validateissuer = true }, notifications = new openidconnectauthenticationnotifications() { authorizationcodereceived = (context) => { var code = context.code; clientcredential credential = new clientcredential(configurationhelper.clientid, configurationhelper.appkey); string userobjectid = context.authenticationticket.identity.findfirst(claimtypes.nameidentifier).value; authenticationcontext authcontext = new authenticationcontext(configurationhelper.azureadauthorizationuri, new inmemorytokencache(userobjectid)); authenticationresult result = authcontext.acquiretokenbyauthorizationcode(code, new uri(httpcontext.current.request.url.getleftpart(uripartial.path)), credential, configurationhelper.azureadgraphresourceuri); return task.fromresult(0); }, redirecttoidentityprovider = (context) => { if (isajaxrequest(context.request)) { context.response.statuscode = 401; // web api only! context.response.headers.remove("set-cookie"); context.state = notificationresultstate.handledresponse; } else { string appbaseurl = context.request.scheme + "://" + context.request.host + context.request.pathbase; context.protocolmessage.redirecturi = appbaseurl + "/" + context.request.querystring; context.protocolmessage.postlogoutredirecturi = appbaseurl; } return task.fromresult(0); }, authenticationfailed = (context) => { // suppress exception context.handleresponse(); return task.fromresult(0); } } }); }
inmemorytokencache
our wrapper aroundtokencache
isajaxrequest
function recognizes ajax call. rest standard asp.net mvc 5 template.
our issue when user access token expires, want refresh , keep going without redirecting user login screen or returning 403 client-side. , how should that?
one way solve problem refresh token few moments before expires. in case, application composed of many single pages served node.js server. after login, store token.expires_in
value in cookie, accessible on server-side.
when user navigate or hit f5 refresh page, server initialize client context tokenexpiresin
. if token expires after 100 minutes, automatically refreshed after 90 minutes.
sample code
angular.module('app').run(function() { var tokenexpiresin = context['tokenexpiresin']; if (tokenexpiresin) { refreshtoken(tokenexpiresin); } // automatically refresh token after delay function refreshtoken(delay) { $log.debug('token refreshed in ' + delay + ' ms'); $timeout(function () { authenticationservice.refreshtoken().then( function (token) { // token refresh successful // broadcast event can react if necessary $rootscope.$broadcast(authenticationservice.events.refresh_token, token); // refresh token again after 1 expires refreshtoken(token.expires_in * 1000 * (90/100); }, function (error) { // token invalid, force logout authenticationservice.logout(); }); }, delay); } });
another wayis use authentication interceptor
angular .module('app') .factory('authenticationhttp401interceptor', authenticationhttp401interceptor) // intercept 401 unauthorized http response backend authenticationhttp401interceptor.$inject = ['$q']; function moauthenticationhttp401interceptor($q) { return { responseerror: function(rejection) { if (rejection.status === 401 && rejection.config.url && rejection.config.url.indexof(context.backend_base_url') === 0 && rejection.headers("www-authenticate") && rejection.headers("www-authenticate").indexof('error="invalid_token"') !== -1 && rejection.headers("www-authenticate").indexof('error_description="the access token expired"') !== -1 ) // or using regexp // if (rejection.status === 401 // && /invalid_token.*the access token expired/.test(rejection.headers("www-authenticate")) // ) { // refresh token here // display overlay while doing if necessary } return $q.reject(rejection); } }; }
source : rfc 6750
Comments
Post a Comment