# Replacing aurelia-authentication

**URL:** <https://discourse.aurelia.io/t/replacing-aurelia-authentication/6453>\
**Category:** Help Requests\
**Created:** [June 5, 2025, 11:55am UTC](https://discourse.aurelia.io/t/replacing-aurelia-authentication/6453 "2025-06-05T11:55:09Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![norgie](https://avatars.discourse-cdn.com/v4/letter/n/57b2e6/32.png) [@norgie](https://discourse.aurelia.io/u/norgie)\
**Post date:** [June 5, 2025, 11:55am UTC](https://discourse.aurelia.io/t/replacing-aurelia-authentication/6453/1 "2025-06-05T11:55:09Z")

</div>

Hi.

I don’t know how many of you are familiar with Duende Software’s IdentityServer and its companion product BFF (back-end for front-end). The latter is a .Net component designed to package the necessary components to secure browser-based frontends with [ASP.NET](http://ASP.NET) Core backends.

We’ve been using IdentityServer for quite a while now and the Aurelia component we use in conjunction with IdentityServer is `aurelia-authentication`. Now as most of us has come to realise, storing access tokens in the browser is a bad idea and the aformentioned BFF component  
is Duende’s solution to that particular issue. One of the features of the BFF is that it does not allow login/logout being made by issuing Ajax-calls, but rather expects the client to assign the correct url(s) to the `window.location.href` property. I’ve made a small test application based on Aurelia 1  
since that’s the Aurelia version we’re using in our production applications. I’ve managed to login like so

```auto
export function login() {
  PLATFORM.location.href = `http://localhost:33444/bff/login?returnUrl=${PLATFORM.location.origin}/`;
}

```

(similarely for logout) and I manually call the `checkAuthStatus()` function in a file called `auth.js`. Not being a full-time front-end developer I find it just a wee bit difficult to replace `aurelia-authentication`. The aforementioned `auth.js` is based off of this code from the BFF SplitHost example ([samples/BFF/v3/SplitHosts/FrontendHost/wwwroot/session.js at main · DuendeSoftware/samples · GitHub](https://github.com/DuendeSoftware/samples/blob/main/BFF/v3/SplitHosts/FrontendHost/wwwroot/session.js)) with the sligth modification that I’ve removed the event listener and also modified the urls. In the production application that’s going to use the BFF instead of accessing our IdentityServer instance directly, we currently have two components for authentication, login.js and logout.js who calls the corresponding `authenticate()` and `logout()` functions in `aurelia-authentication`. In addition we utilise the `AuthenticateStep` from `aurelia-authentication` in our `app.router.config.js`, i.e. `config.addPipelineStep('authorize', AuthenticateStep);`.

After a long introduction I guess my questions are:

1. How can we incorporate the code in `auth.js` (see below) in a way most suited to Auralia? (in the test application there’s just one page handling `login`, `logout` and `getAuthStatus()` (the latter is done in the `attached` event).
2. If required, how do we replace `aurelia-authentication`s `AuthenticateStep` in our route configuration?

I know this is (probably) a lot to ask of the community, but you’ve always stepped up to the challenge in the past, so we’re hoping for it to happen once again 🙂 .

TIA

auth.js:

```auto
import { PLATFORM } from 'aurelia-framework';
export var isAuthenticated : boolean = false;
export var user = null;

export async function chechAuthStatus() {
  try {
    var request = new Request('http://localhost:33444/bff/user', {
      headers: new Headers({
        'X-CSRF': '1'
      }),
      credentials: "include"
    });
    const res = await fetch(request);
    if (res.ok) {
      isAuthenticated = true;
      user = await res.json();
    } else if (res.status === 401) {
      isAuthenticated = false;
      user = null;
    } else {
      console.error('Error checking auth status:', res.status, res.statusText);
      isAuthenticated = false;
      user = null;
    }
  } catch (error) {
    console.error('Network error checking auth status:', error);
    isAuthenticated = false;
    user = null;    
  }
}

export function login() {
  PLATFORM.location.href = `http://localhost:33444/bff/login?returnUrl=${PLATFORM.location.origin}/`;
}

export function logout() {
  if (user) {
    const logoutUrlClaim = user.find(claim => claim.type === 'bff:logout_url');
    if (logoutUrlClaim) {
      console.log(PLATFORM.location.origin);
      PLATFORM.location.href = "http://localhost:33444" + logoutUrlClaim.value + `&returnUrl=${PLATFORM.location.origin}/`;
      return;
    } else {
      console.error("Security Error: bff:logout_url claim not found in user data.");
    }
  }
  // Fallback ONLY if user claims were somehow not loaded (less secure)
  console.warn("User claims not loaded, attempting simple fallback logout.");
  PLATFORM.location.href = `http://localhost:33444/bff/logout?returnUrl=${PLATFORM.location.origin}`;
}

```

---

<div class="post-metadata">

**Author:** ![dwaynecharrington](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/dwaynecharrington/32/11_2.png) [@dwaynecharrington](https://discourse.aurelia.io/u/dwaynecharrington)\
**Post date:** [June 7, 2025, 11:33pm UTC](https://discourse.aurelia.io/t/replacing-aurelia-authentication/6453/2 "2025-06-07T23:33:59Z")

</div>

I’ve implemented authentication myself using singletons in my Aurelia 1 apps. Here is what you would do.

Put every auth-related bit in a singleton so the whole app gets one instance and you can data-bind off it `src/auth-service.ts`

```typescript
// src/auth-service.ts
import { singleton, observable, PLATFORM } from 'aurelia-framework';

@singleton()
export class AuthService {
  @observable public isAuthenticated = false;
  public user: any = null;

  private bff = 'http://localhost:33444'; // adjust once you deploy

  async check(): Promise<void> {
    try {
      const res = await fetch(`${this.bff}/bff/user`, {
        credentials: 'include',
        headers: { 'X-CSRF': '1' }
      });

      if (res.ok) {
        this.isAuthenticated = true;
        this.user = await res.json();
      } else if (res.status === 401) {
        this.isAuthenticated = false;
        this.user = null;
      } else {
        console.error('Auth check failed', res.status, res.statusText);
        this.isAuthenticated = false;
        this.user = null;
      }
    } catch (err) {
      console.error('Network error during auth check', err);
      this.isAuthenticated = false;
      this.user = null;
    }
  }

  login(returnUrl: string = PLATFORM.location.origin): void {
    PLATFORM.location.href =
      `${this.bff}/bff/login?returnUrl=${encodeURIComponent(returnUrl)}`;
  }

  logout(returnUrl: string = PLATFORM.location.origin): void {
    if (this.user) {
      const claim = this.user.find(c => c.type === 'bff:logout_url');
      if (claim) {
        PLATFORM.location.href =
          `${this.bff}${claim.value}&returnUrl=${encodeURIComponent(returnUrl)}`;
        return;
      }
    }
    // fallback
    PLATFORM.location.href =
      `${this.bff}/bff/logout?returnUrl=${encodeURIComponent(returnUrl)}`;
  }
}

```

### **Wire it up when the app starts**

```typescript
// app.ts (or main.ts)
import { inject } from 'aurelia-framework';
import { AuthService } from './auth-service';

@inject(AuthService)
export class App {
  constructor(private auth: AuthService) {}

  async activate() {
    // cookies might already be there, so get current status
    await this.auth.check();
  }
}

```

Anything that needs live status (navbar, etc.) can now bind to auth.isAuthenticated or auth.user.

## Replace AuthenticateStep with your own

A pipeline step in Aurelia 1 is just a class with a run method. We’ll call the service before every navigation and bail out if the user is not logged in.

```typescript
// src/bff-authorize-step.ts
import { Redirect } from 'aurelia-router';
import { AuthService } from './auth-service';

export class BffAuthorizeStep {
  static inject = [AuthService];
  constructor(private auth: AuthService) {}

  async run(navigationInstruction, next) {
    // Any route can opt-out with settings: { auth: false }
    const instructions = navigationInstruction.getAllInstructions();
    if (instructions.some(i => i.config.settings?.auth === false)) {
      return next();
    }

    if (!this.auth.isAuthenticated) {
      await this.auth.check();
    }

    if (this.auth.isAuthenticated) {
      return next();
    }

    // send the user to a simple /login view that just calls auth.login()
    return next.cancel(new Redirect('/login'));
  }
}

```

Then register it:

```typescript
// app.router.config.ts
import { BffAuthorizeStep } from './bff-authorize-step';

export function configureRouter(config, router) {
  // your routes here ...
  config.addPipelineStep('authorize', BffAuthorizeStep);
}

```

If most of your HTTP calls go through Aurelia’s HttpClient, centralise the CSRF header and cookie handling so you never forget.

```typescript
import { HttpClient } from 'aurelia-fetch-client';
import { inject } from 'aurelia-framework';

@inject(HttpClient)
export class Api {
  constructor(private http: HttpClient) {
    http.configure(cfg =>
      cfg.withDefaults({
        credentials: 'include',
        headers: { 'X-CSRF': '1' }
      })
      .withInterceptor({
        response(res) {
          // If session expired mid-app, force a refresh
          if (res.status === 401) window.location.href = '/login';
          return res;
        }
      }));
  }

  // example API call
  async getThings() {
    const res = await this.http.fetch('/api/things');
    return res.json();
  }
}

```

### Quick recap

- All auth state lives in a singleton service.
- A custom pipeline step guards routes, calling the service when needed.
- Login and logout routes are one-liners that just trigger the BFF redirect.
- Fetch client adds the CSRF header and cookies automatically.

Let me know if none or some of that doesn’t make sense. But that’s what I would do.
