diff --git a/__tests__/src/sagas/auth.test.js b/__tests__/src/sagas/auth.test.js
index ab9c6384811327a541e34efa140d5eafc9c9fc6f..229e9956e3a166de4ab843e322029fb1a05b4e12 100644
--- a/__tests__/src/sagas/auth.test.js
+++ b/__tests__/src/sagas/auth.test.js
@@ -8,6 +8,8 @@ import {
   refetchInfoResponses,
   refetchInfoResponsesOnLogout,
   doAuthWorkflow,
+  rerequestOnAccessTokenFailure,
+  invalidateInvalidAuth,
 } from '../../../src/state/sagas/auth';
 import {
   fetchInfoResponse,
@@ -283,4 +285,107 @@ describe('IIIF Authentication sagas', () => {
         .run();
     });
   });
+
+  describe('rerequestOnAccessTokenFailure', () => {
+    it('does nothing if no access token was used', () => {
+      const infoJson = {};
+      const windowId = 'window';
+      const tokenServiceId = undefined;
+      return expectSaga(rerequestOnAccessTokenFailure, { infoJson, tokenServiceId, windowId })
+        .provide([
+          [select(getAccessTokens), {}],
+        ])
+        .not.put.like({ type: ActionTypes.REQUEST_ACCESS_TOKEN })
+        .run();
+    });
+
+    it('does nothing if the access token has never worked', () => {
+      const infoJson = {
+        service: [{
+          '@context': 'http://iiif.io/api/auth/1/context.json',
+          '@id': 'https://authentication.example.com/kiosk',
+          profile: 'http://iiif.io/api/auth/1/kiosk',
+          service: [
+            {
+              '@id': 'https://authentication.example.com/token',
+              profile: 'http://iiif.io/api/auth/1/token',
+            },
+          ],
+        }],
+      };
+      const windowId = 'window';
+      const tokenServiceId = 'https://authentication.example.com/token';
+      return expectSaga(rerequestOnAccessTokenFailure, { infoJson, tokenServiceId, windowId })
+        .provide([
+          [select(getAccessTokens), { [tokenServiceId]: { success: false } }],
+        ])
+        .not.put.like({ type: ActionTypes.REQUEST_ACCESS_TOKEN })
+        .run();
+    });
+
+    it('re-requests the access token if it might be reneweable', () => {
+      const infoJson = {
+        service: [{
+          '@context': 'http://iiif.io/api/auth/1/context.json',
+          '@id': 'https://authentication.example.com/kiosk',
+          profile: 'http://iiif.io/api/auth/1/kiosk',
+          service: [
+            {
+              '@id': 'https://authentication.example.com/token',
+              profile: 'http://iiif.io/api/auth/1/token',
+            },
+          ],
+        }],
+      };
+      const windowId = 'window';
+      const tokenServiceId = 'https://authentication.example.com/token';
+      return expectSaga(rerequestOnAccessTokenFailure, { infoJson, tokenServiceId, windowId })
+        .provide([
+          [select(getAccessTokens), { [tokenServiceId]: { success: true } }],
+        ])
+        .put({
+          authId: 'https://authentication.example.com/kiosk',
+          serviceId: 'https://authentication.example.com/token',
+          type: ActionTypes.REQUEST_ACCESS_TOKEN,
+        })
+        .run();
+    });
+  });
+
+  describe('invalidateInvalidAuth', () => {
+    it('resets the auth service if the auth cookie might have expired', () => {
+      const authId = 'authId';
+      const serviceId = 'serviceId';
+
+      return expectSaga(invalidateInvalidAuth, { serviceId })
+        .provide([
+          [select(getAccessTokens), { [serviceId]: { authId, id: serviceId, success: true } }],
+          [select(getAuth), { [authId]: { id: authId } }],
+        ])
+        .put({
+          id: authId,
+          tokenServiceId: serviceId,
+          type: ActionTypes.RESET_AUTHENTICATION_STATE,
+        })
+        .run();
+    });
+
+    it('marks the auth service as failed if the auth token was not successfully used', () => {
+      const authId = 'authId';
+      const serviceId = 'serviceId';
+
+      return expectSaga(invalidateInvalidAuth, { serviceId })
+        .provide([
+          [select(getAccessTokens), { [serviceId]: { authId, id: serviceId } }],
+          [select(getAuth), { [authId]: { id: authId } }],
+        ])
+        .put({
+          id: authId,
+          ok: false,
+          tokenServiceId: serviceId,
+          type: ActionTypes.RESOLVE_AUTHENTICATION_REQUEST,
+        })
+        .run();
+    });
+  });
 });
diff --git a/src/state/sagas/auth.js b/src/state/sagas/auth.js
index 3ce04f5a7962bc6a6005bab51b1071e1da764801..5f99c8c85d58f31ae07af4cfaaadc2f53242664d 100644
--- a/src/state/sagas/auth.js
+++ b/src/state/sagas/auth.js
@@ -98,9 +98,60 @@ export function* doAuthWorkflow({ infoJson, windowId }) {
     yield put(requestAccessToken(tokenService.id, authService.id));
   }
 }
+
+/** */
+export function* rerequestOnAccessTokenFailure({ infoJson, windowId, tokenServiceId }) {
+  if (!tokenServiceId) return;
+
+  // make sure we have an auth service to try
+  const authService = Utils.getServices(infoJson).find(service => {
+    const tokenService = Utils.getService(service, 'http://iiif.io/api/auth/1/token');
+
+    return tokenService && tokenService.id === tokenServiceId;
+  });
+
+  if (!authService) return;
+
+  // make sure the token ever worked (and might have expired or needs to be re-upped)
+  const accessTokenServices = yield select(getAccessTokens);
+  const service = accessTokenServices[tokenServiceId];
+  if (!(service && service.success)) return;
+
+  yield put(requestAccessToken(tokenServiceId, authService.id));
+}
+
+/** */
+export function* invalidateInvalidAuth({ serviceId }) {
+  const accessTokenServices = yield select(getAccessTokens);
+  const authServices = yield select(getAuth);
+
+  const accessTokenService = accessTokenServices[serviceId];
+  if (!accessTokenService) return;
+  const authService = authServices[accessTokenService.authId];
+  if (!authService) return;
+
+  if (accessTokenService.success) {
+    // if the token ever worked, reset things so we try to get a new cookie
+    yield put(resetAuthenticationState({
+      authServiceId: authService.id,
+      tokenServiceId: accessTokenService.id,
+    }));
+  } else {
+    // if the token never worked, mark the auth service as bad so we could
+    // try to pick a different service
+    yield put(resolveAuthenticationRequest(
+      authService.id,
+      accessTokenService.id,
+      { ok: false },
+    ));
+  }
+}
+
 /** */
 export default function* authSaga() {
   yield all([
+    takeEvery(ActionTypes.RECEIVE_DEGRADED_INFO_RESPONSE, rerequestOnAccessTokenFailure),
+    takeEvery(ActionTypes.RECEIVE_ACCESS_TOKEN_FAILURE, invalidateInvalidAuth),
     takeEvery(ActionTypes.RECEIVE_DEGRADED_INFO_RESPONSE, doAuthWorkflow),
     takeEvery(ActionTypes.RECEIVE_ACCESS_TOKEN, refetchInfoResponses),
     takeEvery(ActionTypes.RESET_AUTHENTICATION_STATE, refetchInfoResponsesOnLogout),