Skip to content

Route Guards (Transition Hooks) ​

Every navigation in UI-Router is a Transition, and transitions can be observed and altered with transition hooks. A hook that redirects or cancels a transition is a route guard. Because guards run in the router — not in components — a single hook can protect any number of states.

This page walks through the authentication guard the sample app uses (requiresAuth.hook.ts).

Mark states that need protection ​

State declarations accept an arbitrary data object. Child states inherit (and may override) their parent's data, so marking one parent protects a whole subtree:

ts
const contactsState: LitStateDeclaration = {
  name: 'contacts',
  url: '/contacts',
  component: Contacts,
  data: { requiresAuth: true },
};

// contacts.detail, contacts.edit, … inherit requiresAuth automatically

Write the guard ​

A hook has two parts: criteria choosing which transitions it applies to, and a callback that decides what happens. Returning a TargetState redirects the transition; returning false cancels it; returning nothing lets it proceed.

ts
import { StateObject, Transition } from '@uirouter/core';

const requiresAuthHook = {
  // Matches transitions to any state whose data has a truthy `requiresAuth`
  criteria: {
    to: (state: StateObject | undefined) =>
      state?.data && state.data.requiresAuth,
  },
  // Redirect to login if the user is not authenticated
  callback: (transition: Transition) => {
    const $state = transition.router.stateService;
    if (!AuthService.isAuthenticated()) {
      return $state.target('login', undefined, { location: false });
    }
  },
};

{ location: false } keeps the attempted URL in the address bar during the redirect, so logging in can return the user to where they were headed.

Register it ​

ts
router.transitionService.onBefore(
  requiresAuthHook.criteria,
  requiresAuthHook.callback,
  { priority: 10 },
);

onBefore runs before the transition starts any work (before resolves fetch). Higher priority hooks run first. The TransitionService offers the full lifecycle — onBefore, onStart, onEnter, onRetain, onExit, onFinish, onSuccess, onError — and all of them accept the same criteria/callback shape.

The transition lifecycle and where guards runonBeforeonStartonExit · onRetain · onEnteronFinishonSuccessonErrorviewsrenderon the nextLit updatecancelledor failedresolves fetch during the transition — data arrives before any view rendersa route guard is an onBefore hookcriteria: { to: (s) => s.data.requiresAuth }TargetState → redirect (e.g. to login)false → cancel the transitionnothing → proceedguards can be async — the transitionwaits for the returned Promise

Return the user after login ​

The redirect above targets a login state. To send the user back where they were going, the login state resolves its return target from the transition that redirected to it:

ts
const loginState: LitStateDeclaration = {
  name: 'login',
  url: '/login',
  component: Login,
  resolve: [{ token: 'returnTo', deps: ['$transition$'], resolveFn: returnTo }],
};

/** The state to return to after a successful login. */
function returnTo($transition$: Transition) {
  // Redirected here by the guard? Return to the original target.
  if ($transition$.redirectedFrom()) {
    return $transition$.redirectedFrom().targetState();
  }

  const $state = $transition$.router.stateService;

  // Navigated here directly? Go back to the previous state.
  if ($transition$.from().name !== '') {
    return $state.target($transition$.from(), $transition$.params('from'));
  }

  // Otherwise (deep-linked straight to /login), fall back to home.
  return $state.target('home');
}

After authenticating, the login component navigates to the resolved target:

ts
const { returnTo } = this._uiViewProps.resolves;
router.stateService.go(returnTo.state(), returnTo.params(), {
  reload: true,
});

Async guards ​

Hook callbacks may return a Promise; the transition waits for it. That makes guards a natural place for "check the session with the server" logic:

ts
router.transitionService.onBefore(criteria, async (transition) => {
  const ok = await AuthService.checkSession();
  if (!ok) {
    return transition.router.stateService.target('login');
  }
});

Guards in components ​

For per-component concerns — like an edit form warning about unsaved changes before the user navigates away — implement the uiCanExit lifecycle hook on the routed component instead of a global guard. See the UiOnExit interface.