OAuth
npm install @feathersjs/authentication-oauth --save
@feathersjs/authentication-oauth
allows to authenticate with over 180 OAuth providers (Google, Facebook, GitHub etc.) using grant, an OAuth middleware module for NodeJS.
Usage
The following section covers oAuth authentication strategy setup and a more detailed description of the possible oAuth flows and oAuth URLs.
Setup
The following is a standard oAuth setup. The OAuthStrategy
often needs to be customized to include additional fields (like the avatar, email address or username) from the oAuth provider.
import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'
import { LocalStrategy } from '@feathersjs/authentication-local'
import { OAuthStrategy, oauth } from '@feathersjs/authentication-oauth'
import type { Application } from './declarations'
declare module './declarations' {
interface ServiceTypes {
authentication: AuthenticationService
}
}
export const authentication = (app: Application) => {
const authentication = new AuthenticationService(app)
authentication.register('jwt', new JWTStrategy())
authentication.register('github', new OAuthStrategy())
authentication.register('google', new OAuthStrategy())
app.use('authentication', authentication)
app.configure(oauth({}))
}
The following settings for app.configure(oauth())
are available:
linkStrategy
(default:'jwt'
) - The name of the stratagy to use for account linkingauthService
(default:app.get('defaultAuthentication')
) - The path of the authentication service to useexpressSession
- An Express middleware for handling sessions. By default will use an HTTP cookie that is only available for the oAuth flow. This normally does not need to be changed.koaSession
- A Koa middleware for handling sessions. By default will use an HTTP cookie that is only available for the oAuth flow. This normally does not need to be changed.
Providers
For specific OAuth provider setup see the following cookbook guides:
Flow
There are two ways to initiate OAuth authentication:
Through the browser (most common)
- User clicks on link to OAuth URL (
oauth/<provider>
) - Gets redirected to provider and authorizes the application
- Callback to the OauthStrategy which
- Gets the users profile
- Finds or creates the user (entity) for that profile
- The AuthenticationService creates an access token for that entity
- Redirects back to the origin URL including the generated access token
- The frontend (e.g. the Feathers authentication client) uses the returned access token to authenticate
- User clicks on link to OAuth URL (
With an existing access token, e.g. obtained through the Facebook mobile SDK
- Authenticate normally through the authentication service with
{ strategy: '<name>', accessToken: 'oauth access token' }
. - Calls the OauthStrategy which
- Gets the users profile
- Finds or creates the entity for that profile
- Returns the authentication result
- Authenticate normally through the authentication service with
Important
If you are attempting to authenticate using an existing oAuth access token, ensure that you have added the strategy (e.g. 'facebook') to the allowed authStrategies configuration.
OAuth URLs
There are two URLs and redirects that are important for OAuth authentication:
http(s)://<host>/oauth/<provider>
: The main URL to initiate the OAuth flow. Link to this from the browser.http(s)://<host>/oauth/<provider>/callback
: The callback path that should be set in the OAuth provider application settings.
In the browser any OAuth flow can be initiated with a link like this:
<a href="/oauth/github">Login with GitHub</a>
Account linking
To link an existing user the current access token can be added to the OAuth flow query using the feathers_token
query parameter:
<a href="/oauth/github?feathers_token=<your access token>"> Login with GitHub </a>
This will use the user (entity) of that access token to link the OAuth account to. Using the authentication client you can get the current access token via app.get('authentication')
:
const { accessToken } = await app.get('authentication')
Redirects
The recommended way to enable redirects is to set a list of allowed origins
in your application configuration (e.g. config/default.json
). This will ensure cross origin restrictions across your application and allow oAuth authentication from different frontend applications.
{
"origins": [
// Allow redirect to a local development frontend
// (e.g. a create-react-app server) and our production URL
"http://localhost:500", "https://myapp.feathersjs.com"
]
}
Alternatively, the redirect
configuration can be used to redirect back to the frontend application after OAuth authentication was successful and an access token for the user has been created by the authentication service or if authentication failed. It works cross domain and by default includes the access token or error message in the window location hash. The following configuration
{
"authentication": {
"oauth": {
"redirect": "https://app.mydomain.com/"
}
}
}
A successful oAuth flow will redirect to the origin or redirect URL in the form of https://app.mydomain.com/#access_token=<user jwt>
or https://app.mydomain.com/#error=<some error message>
. Redirects can be customized with the getRedirect() method of the OAuth strategy. The authentication client handles the default redirects automatically already.
Note
The redirect is using a hash instead of a query string by default because it is not logged server side and can be easily read on the client. You can force query based redirect by adding a ?
to the end of the redirect
option.
If the redirect
option is not set and no origin is available the authentication result data will be sent as JSON instead.
Dynamic redirects to the same URL are possible by setting the redirect
query parameter in the OAuth flow. For example, the following OAuth link:
<a href="/oauth/github?redirect=dashboard"> Login with GitHub </a>
With the above configuration will redirect to https://app.mydomain.com/dashboard
after the OAuth flow.
Options
Options are usually set under the registered name via Feathers configuration in config/default.json
or config/<environment>.json
under the authentication.oauth
section. Available options are:
origins
(default:app.get('origins')
): A list of URLs from which oAuth authentication should be allowed. For example setting this option to[ "https://feathersjs.com", "https://feathers.cloud" ]
would allow requests from those domains and redirect back to where the request came from. This can be used instead of theredirect
option for a more consisten cross origin configuration via theapp.get('origins')
configuration value and to allow oAuth logins from multiple domains.redirect
: The URL of the frontend to redirect to with the access token (or error message). The authentication client handles those redirects automatically. If not set, the authentication result will be sent as JSON instead.defaults
: Default Grant configuration used for all strategies. The following default options are set automatically:prefix
(default:'/oauth'
) - The OAuth base pathorigin
(default:http(s)://host[:port]
) - The server path for the oAuth flow to redirect back to. Set this if you are e.g. running your local server on HTTPS
<strategy-name>
(e.g.twitter
): The Grant configuration used for a specific strategy.
tip
Removing the redirect
option allows to troubleshoot troubleshoot OAuth authentication errors as a JSON response by opening the oAuth URL directly in the browser.
{
"authentication": {
"origins": ["localhost:3030"],
"oauth": {
"redirect": "/frontend",
"google": {
"key": "...",
"secret": "...",
"custom_params": { "access_type": "offline" }
},
"twitter": {
"key": "...",
"secret": "..."
}
}
}
}
Note
All OAuth strategies will by default always look for configuration under authentication.oauth.<name>
. If authentication.oauth
is not set in the configuration, OAuth authentication will be disabled.
Here is a list of all Grant configuration options that are available:
Key | Location | Description |
---|---|---|
request_url | oauth.json | OAuth1/step1 |
authorize_url | oauth.json | OAuth1/step2 or OAuth2/step1 |
access_url | oauth.json | OAuth1/step3 or OAuth2/step2 |
oauth | oauth.json | OAuth version number |
scope_delimiter | oauth.json | string delimiter used for concatenating multiple scopes |
protocol, host, path | defaults | used to generate redirect_uri |
transport | defaults | transport to use to deliver the response data in your final callback route |
state | defaults | toggle random state string generation for OAuth2 |
key | [provider] | OAuth app key, reserved aliases: consumer_key and client_id |
secret | [provider] | OAuth app secret, reserved aliases: consumer_secret and client_secret |
scope | [provider] | list of scopes to request |
custom_params | [provider] | custom authorization parameters and their values |
subdomain | [provider] | string to be embedded in request_url , authorize_url and access_url |
nonce | [provider] | toggle random nonce string generation for OpenID Connect providers |
callback | [provider] | final callback route on your server to receive the response data |
dynamic | [provider] | allow dynamic override of configuration |
overrides | [provider] | static overrides for a provider |
response | [provider] | limit the response data |
token_endpoint_auth_method | [provider] | authentication method for the token endpoint |
name | generated | provider's name, used to generate redirect_uri |
profile_url | grant-profile | The URL to retrieve the user profile from |
[provider] | generated | provider's name as key |
redirect_uri | generated | OAuth app redirect URI, generated using protocol , host , path and name |
OAuthStrategy
entityId
oauthStrategy.entityId -> string
returns the name of the id property of the entity.
getEntityQuery(profile, params)
oauthStrategy.getEntityQuery(profile, params) -> Promise
returns the entity lookup query to find the entity for a profile. By default returns
{
[`${this.name}Id`]: profile.sub || profile.id
}
getEntityData(profile, entity, params)
oauthStrategy.getEntityData(profile, existing, params) -> Promise
returns the data to either create a new or update an existing entity. entity
is either the existing entity or null
when creating a new entity.
getProfile(data, params)
oauthStrategy.getProfile(data, params) -> Promise
returns the user profile information from the OAuth provider that was used for the login. data
is the OAuth callback information which normally contains e.g. the OAuth access token.
getRedirect (data)
oauthStrategy.getRedirect(data) -> Promise
returns the URL to redirect to after a successful OAuth login and entity lookup or creation. By default it redirects to authentication.oauth.redirect
from the configuration with #access_token=<access token for entity>
added to the end of the URL. The access_token
hash is e.g. used by the authentication client to log the user in after a successful OAuth login. The default redirects do work cross domain.
getAllowedOrigin (params)
oauthStrategy.getAllowedOrigin(params) -> Promise
returns the redirect base URL or throws an error if it is not allowed.
getCurrentEntity(params)
oauthStrategy.getCurrentEntity(params) -> Promise
returns the currently linked entity for the given params
. It will either use the entity authenticated by params.authentication
or return null
.
findEntity(profile, params)
oauthStrategy.findEntity(profile, params) -> Promise
finds an entity for a given OAuth profile. Uses { [${this.name}Id]: profile.id }
by default.
createEntity(profile, params)
oauthStrategy.createEntity(profile, params) -> Promise
creates a new entity for the given OAuth profile. Uses { [${this.name}Id]: profile.id }
by default.
updateEntity(entity, profile, params)
oauthStrategy.updateEntity(entity, profile, params) -> Promise
updates an existing entity with the given profile. Uses { [${this.name}Id]: profile.id }
by default.
authenticate(authentication, params)
oauthStrategy.authenticate(authentication, params)
is the main endpoint implemented by any authentication strategy. It is usually called for authentication requests for this strategy by the AuthenticationService.
Customization
Normally, any OAuth provider set up in the configuration will be initialized with the default OAuthStrategy. The flow for a specific provider can be customized by extending OAuthStrategy
class and registering it under that name on the AuthenticationService:
import { Application } from '@feathersjs/feathers'
import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'
import type { OAuthProfile } from '@feathersjs/authentication'
import { OAuthStrategy } from '@feathersjs/authentication-oauth'
declare module './declarations' {
interface ServiceTypes {
authentication: AuthenticationService
}
}
class MyGithubStrategy extends OAuthStrategy {
async getEntityData(profile: OAuthProfile) {
// Include the `email` from the GitHub profile when creating
// or updating a user that logged in with GitHub
const baseData = await super.getEntityData(profile)
return {
...baseData,
email: profile.email
}
}
}
export default (app: Application) => {
const authentication = new AuthenticationService(app)
authentication.register('github', new MyGithubStrategy())
// ...
app.use('authentication', authentication)
}