Integrating Talon Auth into a Feathers Application

This guide walks through replacing local (email/password) and OAuth authentication in a FeathersJS application with Talon Auth — a passwordless authentication system based on login codes. The examples are based on the feathers-chat application.

Prerequisites

  • A Feathers v5 application with @feathersjs/authentication already configured
  • A Talon application. You can create one at the Talon Dashboard. Note your App ID (a did:key:... string).

1. Install the talon-auth package

npm install talon-auth

2. Update the authentication configuration

In your config/default.json, replace the local strategy (and any OAuth strategies) with talon in the authStrategies array, and remove the local and oauth configuration blocks:

{
  "authentication": {
    "secret": "your-secret",
    "authStrategies": [
      "jwt",
      "talon"
    ],
    "jwtOptions": {
      "header": {
        "typ": "access"
      },
      "audience": "https://yourdomain.com",
      "algorithm": "HS256",
      "expiresIn": "1d"
    }
  }
}

The local, oauth, and any provider-specific blocks (e.g. github) can be removed entirely.

3. Create the Talon authentication strategy

Replace the contents of src/authentication.ts with a custom TalonStrategy that uses the talon-auth verifier to validate tokens and find-or-create users:

import { AuthenticationBaseStrategy, AuthenticationService, JWTStrategy } from '@feathersjs/authentication'
import { createVerifier } from 'talon-auth'
import type { Application } from './declarations'

declare module './declarations' {
  interface ServiceTypes {
    authentication: AuthenticationService
  }
}

const appId = 'your-talon-app-id'

class TalonStrategy extends AuthenticationBaseStrategy {
  verifier = createVerifier({ appId })

  async authenticate(authentication: any, params: any) {
    const { accessToken } = authentication
    const { user: talonUser } = await this.verifier.verify(accessToken)
    const usersService = this.app!.service('users')

    if (!talonUser) {
      throw new Error('Talon user not found')
    }

    const { email } = talonUser
    // Find or create the user by email
    const users = await usersService.find({
      query: { email },
      paginate: false
    })
    const user = users?.length > 0 ? users[0] : await usersService.create({ email })

    return {
      authentication: { strategy: 'talon' },
      user
    }
  }
}

export const authentication = (app: Application) => {
  const authentication = new AuthenticationService(app)

  authentication.register('jwt', new JWTStrategy())
  authentication.register('talon', new TalonStrategy())

  app.use('authentication', authentication)
}

4. Simplify the user schema

Since Talon handles authentication, you can remove password-related fields and password hashing from your user schema. In src/services/users/users.schema.ts:

  • Remove the password and any provider ID fields (e.g. githubId) from userSchema
  • Remove the passwordHash import and resolver from @feathersjs/authentication-local
  • Remove the password field from userExternalResolver, userDataSchema, and userPatchResolver

Before:

import { passwordHash } from '@feathersjs/authentication-local'

export const userSchema = Type.Object({
  id: Type.Number(),
  email: Type.String(),
  password: Type.Optional(Type.String()),
  githubId: Type.Optional(Type.Number()),
  avatar: Type.Optional(Type.String())
}, { $id: 'User', additionalProperties: false })

export const userDataSchema = Type.Pick(userSchema, ['email', 'password', 'githubId', 'avatar'], {
  $id: 'UserData',
  additionalProperties: false
})

export const userDataResolver = resolve<User, HookContext>({
  password: passwordHash({ strategy: 'local' }),
  avatar: async (value, user) => { /* ... */ }
})

After:

export const userSchema = Type.Object({
  id: Type.Number(),
  email: Type.String(),
  avatar: Type.Optional(Type.String())
}, { $id: 'User', additionalProperties: false })

export const userDataSchema = Type.Pick(userSchema, ['email', 'avatar'], {
  $id: 'UserData',
  additionalProperties: false
})

export const userDataResolver = resolve<User, HookContext>({
  avatar: async (value, user) => { /* ... */ }
})

Similarly, update userQueryProperties to remove any provider ID fields:

export const userQueryProperties = Type.Pick(userSchema, ['id', 'email'])

5. Add the <talon-login> web component to the frontend

The <talon-login> element is a drop-in Lit web component that handles the entire login flow (email input, code verification) and hides itself once authenticated.

Add it to your index.html:

<talon-login
  id="talon-login"
  app-id="your-talon-app-id"
></talon-login>
<script type="module" src="https://esm.sh/talon-auth/login"></script>

Place it before your application scripts so it is available when they run.

6. Update the client-side JavaScript

Replace the login/signup form logic with Talon's token-based flow. The key change is using talonLogin.getAccessToken() to obtain a token, then passing it to Feathers:

const socket = io()
const client = feathers.socketio(socket)
client.configure(feathers.authentication())

// Reference the <talon-login> element
const talonLogin = document.getElementById('talon-login')

const initialize = async () => {
  // getAccessToken() shows the login UI if needed,
  // and resolves with a JWT once the user is authenticated
  const accessToken = await talonLogin.getAccessToken()

  await client.authenticate({
    strategy: 'talon',
    accessToken
  })

  // User is now authenticated — show the app
  showChat()
}

initialize()

For logout, call both the Feathers client logout and the Talon element's logout:

addEventListener('#logout', 'click', async () => {
  await client.logout()
  await talonLogin.logout()
})

You can remove all login/signup form templates, the getCredentials() helper, and the login() function — Talon's web component handles the entire authentication UI.

7. Clean up unused dependencies

You can remove packages that are no longer needed:

npm uninstall @feathersjs/authentication-local @feathersjs/authentication-oauth

Summary

What changedBeforeAfter
Auth strategiesjwt, local (+ optional OAuth)jwt, talon
User fieldsemail, password, githubIdemail
Login UICustom form in the app<talon-login> web component
Dependencies@feathersjs/authentication-local, @feathersjs/authentication-oauthtalon-auth
Backend verificationPassword hash comparisonOffline JWT verification via createVerifier
Subscribe to our NewsletterGet new Feathers content as it becomes available.
Talon Auth logo

Copyright © 2012 - 2026 feathers.dev