The @feathersjs/authentication-oauth package provides OAuth 2.0 authentication for various providers like Google, Facebook, GitHub, and more.
Installation
npm install @feathersjs/authentication-oauth
Setup
Configure and register OAuth authentication:
import { oauth, OAuthStrategy } from '@feathersjs/authentication-oauth'
app.configure(oauth(options))
Options
interface OauthSetupSettings {
linkStrategy?: string
authService?: string
expressSession?: RequestHandler
koaSession?: Middleware
}
The strategy to use for linking existing accounts
The path to the authentication service (defaults to app default)
Custom Express session middleware
Custom Koa session middleware
Configuration
Configure OAuth providers in your authentication settings:
interface OAuthConfiguration {
redirect?: string
origins?: string[]
defaults?: {
prefix?: string
origin?: string
transport?: string
response?: string[]
}
[provider: string]: {
key: string
secret: string
scope?: string[]
custom_params?: object
[key: string]: any
}
}
The URL to redirect to after authentication. If not set, will use the first origin in origins
Array of allowed redirect origins for security. Supports multiple origins for different environments
Default settings for all OAuth providers:
prefix: URL prefix for OAuth routes (default: ‘/oauth’)
origin: Server origin URL
transport: How to send the response (default: ‘state’)
response: What to include in response (default: [‘tokens’, ‘raw’, ‘profile’])
Configuration for each OAuth provider (e.g., ‘google’, ‘github’):
key: OAuth client ID
secret: OAuth client secret
scope: Requested OAuth scopes
custom_params: Additional provider-specific parameters
Example:
// config/default.json
{
"authentication": {
"secret": "your-secret-key",
"authStrategies": ["jwt", "local", "google", "github"],
"service": "users",
"entity": "user",
"oauth": {
"redirect": "http://localhost:3000",
"origins": [
"http://localhost:3000",
"https://yourdomain.com"
],
"google": {
"key": "your-google-client-id",
"secret": "your-google-client-secret",
"scope": ["email", "profile"],
"custom_params": {
"access_type": "offline"
}
},
"github": {
"key": "your-github-client-id",
"secret": "your-github-client-secret",
"scope": ["user", "repo"]
},
"facebook": {
"key": "your-facebook-app-id",
"secret": "your-facebook-app-secret"
}
}
}
}
OAuthStrategy
Base class for OAuth authentication strategies.
Setup
Register OAuth strategies:
import { AuthenticationService } from '@feathersjs/authentication'
import { oauth, OAuthStrategy } from '@feathersjs/authentication-oauth'
// OAuth will automatically register strategies for each configured provider
app.configure(oauth())
// Or manually register custom OAuth strategies
class GoogleStrategy extends OAuthStrategy {
async getProfile(data, params) {
// Custom profile parsing
return data.profile
}
}
authService.register('google', new GoogleStrategy())
Methods
getEntityQuery
Build the query to find an existing entity by OAuth profile. Override to customize.
strategy.getEntityQuery(
profile: OAuthProfile,
params: Params
): Promise<Query>
The OAuth profile from the providerinterface OAuthProfile {
id?: string | number
[key: string]: any
}
Default behavior:
return {
[`${this.name}Id`]: profile.sub || profile.id
}
Example:
class GoogleStrategy extends OAuthStrategy {
async getEntityQuery(profile, params) {
return {
googleId: profile.sub,
// Also match by email if provided
email: profile.email
}
}
}
getEntityData
Get the data to store when creating or updating an entity. Override to customize.
strategy.getEntityData(
profile: OAuthProfile,
existingEntity: any,
params: Params
): Promise<object>
The OAuth profile from the provider
The existing entity if found, null otherwise
Default behavior:
return {
[`${this.name}Id`]: profile.sub || profile.id
}
Example:
class GoogleStrategy extends OAuthStrategy {
async getEntityData(profile, existingEntity, params) {
return {
googleId: profile.sub,
email: profile.email,
name: profile.name,
picture: profile.picture,
// Only set on first creation
...(!existingEntity && {
createdViaOAuth: true,
emailVerified: profile.email_verified
})
}
}
}
getProfile
Extract the profile from the authentication data. Override to customize.
strategy.getProfile(
data: AuthenticationRequest,
params: Params
): Promise<OAuthProfile>
data
AuthenticationRequest
required
The authentication request data
Example:
class GoogleStrategy extends OAuthStrategy {
async getProfile(data, params) {
const profile = data.profile
// Add custom fields or transform profile
return {
...profile,
customField: 'value'
}
}
}
getRedirect
Get the redirect URL after authentication. Override to customize.
strategy.getRedirect(
data: AuthenticationResult | Error,
params?: AuthenticationParams
): Promise<string | null>
data
AuthenticationResult | Error
required
The authentication result or error
Example:
class GoogleStrategy extends OAuthStrategy {
async getRedirect(data, params) {
const baseRedirect = await super.getRedirect(data, params)
// Add custom query parameters
if (data.user?.isNewUser) {
return `${baseRedirect}&onboarding=true`
}
return baseRedirect
}
}
getCurrentEntity
Get the currently authenticated entity for account linking.
strategy.getCurrentEntity(params: Params): Promise<any>
Service call parameters (should include authentication info)
findEntity
Find an entity by OAuth profile.
strategy.findEntity(
profile: OAuthProfile,
params: Params
): Promise<any>
createEntity
Create a new entity from OAuth profile.
strategy.createEntity(
profile: OAuthProfile,
params: Params
): Promise<any>
updateEntity
Update an existing entity with OAuth profile data.
strategy.updateEntity(
entity: any,
profile: OAuthProfile,
params: Params
): Promise<any>
OAuth Flow
The OAuth authentication flow:
1. Initiate OAuth Flow
Client redirects to:
Example:
<a href="/oauth/google">Login with Google</a>
2. Provider Callback
After user authorizes, provider redirects to:
GET /oauth/:provider/callback
3. Client Receives Token
Client is redirected to the configured redirect URL with the access token:
http://localhost:3000/#access_token=<jwt>
Or with an error:
http://localhost:3000/#error=<error_message>
Account Linking
Link an OAuth account to an existing authenticated user:
// Client-side: Pass JWT token as query parameter
window.location.href = `/oauth/google?feathers_token=${existingJWT}`
The OAuth strategy will:
- Authenticate the existing token
- Get the current user
- Update the user with OAuth provider information
- Return a new JWT
Client Usage
Browser
import { feathers } from '@feathersjs/feathers'
import rest from '@feathersjs/rest-client'
import authClient from '@feathersjs/authentication-client'
const app = feathers()
app.configure(rest('http://localhost:3030').fetch(window.fetch))
app.configure(authClient())
// Redirect to OAuth provider
const loginWithGoogle = () => {
window.location.href = 'http://localhost:3030/oauth/google'
}
// Handle OAuth callback (on your redirect page)
app.reAuthenticate()
.then(result => {
console.log('Authenticated via OAuth', result.user)
})
.catch(error => {
console.error('OAuth authentication failed', error)
})
// Link OAuth account to existing user
const linkGoogleAccount = async () => {
const token = await app.authentication.getAccessToken()
window.location.href = `http://localhost:3030/oauth/google?feathers_token=${token}`
}
React Example
import React, { useEffect, useState } from 'react'
import app from './feathers-client'
function OAuth() {
const [user, setUser] = useState(null)
const [error, setError] = useState(null)
useEffect(() => {
// Handle OAuth callback
app.reAuthenticate()
.then(result => setUser(result.user))
.catch(err => setError(err.message))
}, [])
const handleGoogleLogin = () => {
window.location.href = 'http://localhost:3030/oauth/google'
}
const handleGitHubLogin = () => {
window.location.href = 'http://localhost:3030/oauth/github'
}
if (error) {
return <div>Error: {error}</div>
}
if (user) {
return <div>Welcome, {user.name}!</div>
}
return (
<div>
<button onClick={handleGoogleLogin}>
Login with Google
</button>
<button onClick={handleGitHubLogin}>
Login with GitHub
</button>
</div>
)
}
Custom Redirect Pages
Customize the redirect with query parameters:
// Redirect to specific page after login
window.location.href = '/oauth/google?redirect=/dashboard'
// Custom OAuth strategy to handle custom redirects
class GoogleStrategy extends OAuthStrategy {
async getRedirect(data, params) {
const customPath = params.query?.redirect || ''
const origin = await this.getAllowedOrigin(params)
if (data.accessToken) {
return `${origin}${customPath}#access_token=${data.accessToken}`
}
return `${origin}${customPath}#error=${data.message}`
}
}
Complete Server Example
import { feathers } from '@feathersjs/feathers'
import express from '@feathersjs/express'
import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'
import { oauth, OAuthStrategy } from '@feathersjs/authentication-oauth'
const app = express(feathers())
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.configure(express.rest())
// Configure authentication
app.set('authentication', {
secret: 'your-secret-key',
authStrategies: ['jwt', 'google', 'github'],
service: 'users',
entity: 'user',
oauth: {
redirect: 'http://localhost:3000',
origins: ['http://localhost:3000'],
google: {
key: process.env.GOOGLE_CLIENT_ID,
secret: process.env.GOOGLE_CLIENT_SECRET,
scope: ['email', 'profile']
},
github: {
key: process.env.GITHUB_CLIENT_ID,
secret: process.env.GITHUB_CLIENT_SECRET,
scope: ['user']
}
}
})
// Custom Google strategy
class GoogleStrategy extends OAuthStrategy {
async getEntityData(profile, existingEntity, params) {
return {
googleId: profile.sub,
email: profile.email,
name: profile.name,
picture: profile.picture,
emailVerified: profile.email_verified
}
}
}
// Custom GitHub strategy
class GitHubStrategy extends OAuthStrategy {
async getEntityData(profile, existingEntity, params) {
return {
githubId: profile.id,
email: profile.email,
username: profile.login,
name: profile.name,
avatar: profile.avatar_url
}
}
}
// Setup users service
app.use('users', {
async find(params) {
return []
},
async get(id, params) {
// Get user logic
return { id }
},
async create(data, params) {
// Create user logic
return { id: 1, ...data }
},
async patch(id, data, params) {
// Update user logic
return { id, ...data }
}
})
// Setup authentication
const authService = new AuthenticationService(app)
authService.register('jwt', new JWTStrategy())
authService.register('google', new GoogleStrategy())
authService.register('github', new GitHubStrategy())
app.use('authentication', authService)
// Configure OAuth
app.configure(oauth())
app.listen(3030)
Security
Origin Validation
OAuth redirects are validated against the origins array to prevent open redirect vulnerabilities:
{
"oauth": {
"origins": [
"http://localhost:3000",
"https://app.yourdomain.com",
"https://staging.yourdomain.com"
]
}
}
Redirect Path Validation
Redirect paths are validated to prevent URL authority injection. Paths containing @, \\, or // are rejected.
State Management
OAuth state is managed securely using sessions with CSRF protection.
Supported Providers
The package supports any OAuth 2.0 provider through the Grant library. Common providers include:
- Google
- GitHub
- Facebook
- Twitter
- LinkedIn
- Microsoft
- Auth0
- And 200+ more
See the Grant documentation for the complete list and provider-specific configuration.