Social Authentication
Social authentication is available in Foal v1.3.0 onwards.
FoalTS social authentication is based on OAuth2 protocol. To set up social authentication with Foal, you first need to register your application to the social provider you chose (Google, Facebook, etc). This can be done through its website.
Usually your are required to provide:
- an application name,
- a logo,
- and redirect URIs where the social provider should redirect the users after successful authentication (ex:
http://localhost:3001/signin/google/callback
,https://example.com/signin/facebook/callback
)
Once done, you should receive:
- a client ID that is public and identifies your application,
- and a client secret that must not be revealed and can therefore only be used on the backend side.
You must obtain a client secret. If you do not have one, it means you probably chose the wrong option at some point.
Configuration
First install the appropriate package.
npm install @foal/social
Then, you will need to provide your client ID, client secret and your redirect URIs to Foal. This can be done through the usual configuration files.
default.yml
settings:
social:
google:
clientId: xxx
redirectUri: 'http://localhost:3001/signin/google/callback'
production.yml
settings:
social:
cookie:
secure: true # In production, your website should use HTTPS.
google:
redirectUri: 'https://example.com/signin/google/callback' # Your redirect URI in production
.env
SETTINGS_SOCIAL_GOOGLE_CLIENT_SECRET=yyy
Social Providers
Authentication is managed by social providers. These are services whose methods can be called within a controller to build a social login.
// 3p
import {
Context,
dependency,
Get,
HttpResponseRedirect,
setSessionCookie,
} from '@foal/core';
import { GoogleProvider } from '@foal/social';
import { User } from '../entities';
export class AuthController {
@dependency
google: GoogleProvider;
@Get('/signin/google')
redirectToGoogle() {
// Your "Login In with Google" button should point to this route.
// The user will be redirected to Google auth page.
return this.google.redirect();
}
@Get('/signin/google/callback')
async handleGoogleRedirection(ctx: Context) {
// Once the user gives their permission to login with Google, the provider
// will redirect the user to this route. This route must match the redirect URI.
const { userInfo, tokens } = await this.google.getUserInfo(ctx);
// Do something with the user information AND/OR the access token.
// If you only need the access token, you can call the "getTokens" method.
// The method usually ends with a HttpResponseRedirect object as returned value.
}
}
You can also override the scopes in the redirect
method:
return this.google.redirect({ scopes: [ 'email' ] });
Additional parameters can passed to the redirect
and getUserInfo
methods depending on the provider.
Google
Service name | Default scopes | Available scopes |
---|---|---|
GoogleProvider | openid , profile , email | Google scopes |
Register an OAuth application
Visit the Google API Console to obtain a client ID and a client secret.
Redirection parameters
The redirect
method of the GoogleProvider
accepts additional parameters. These parameters and their description are listed here and are all optional.
Example
this.google.redirect({ /* ... */ }, {
access_type: 'offline'
})
Facebook
Service name | Default scopes | Available scopes |
---|---|---|
FacebookProvider | email | Facebook permissions |
Register an OAuth application
Visit Facebook's developper website to create an application and obtain a client ID and a client secret.
Redirection parameters
The redirect
method of the FacebookProvider
accepts an additional auth_type
parameter which is optional.
Example
this.facebook.redirect({ /* ... */ }, {
auth_type: 'rerequest'
});
Name | Type | Description |
---|---|---|
auth_type | 'rerequest' | If a user has already declined a permission, the Facebook Login Dialog box will no longer ask for this permission. The auth_type parameter explicity tells Facebook to ask the user again for the denied permission. |
User info parameters
The getUserInfo
and getUserInfoFromTokens
methods of the FacebookProvider
accept an additional fields
parameter which is optional.
Example
const { userInfo } = await this.facebook.getUserInfo(ctx, {
fields: [ 'email' ]
})
Name | Type | Description |
---|---|---|
fields | string[] | List of fields that the returned user info object should contain. These fields may or may not be available depending on the permissions (scopes ) that were requested with the redirect method. Default: ['id', 'name', 'email']. |
Github
Github provider is available in Foal v1.4.0 onwards.
Service name | Default scopes | Available scopes |
---|---|---|
GithubProvider | none | Github scopes |
Register an OAuth application
Visit this page to create an application and obtain a client ID and a client secret.
Additional documentation on Github's redirect URLs can be found here.
Redirection parameters
The redirect
method of the GithubProvider
accepts additional parameters. These parameters and their description are listed below and are all optional.
Example
this.github.redirect({ /* ... */ }, {
allow_signup: false
})
Name | Type | Description |
---|---|---|
login | string | Suggests a specific account to use for signing in and authorizing the app. |
allow_signup | boolean | Whether or not unauthenticated users will be offered an option to sign up for GitHub during the OAuth flow. The default is true . Use false in the case that a policy prohibits signups. |
Source: https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#parameters
LinkedIn
LinkedIn provider is available in Foal v1.4.0 onwards.
Service name | Default scopes | Available scopes |
---|---|---|
LinkedInProvider | r_liteprofile | API documentation |
Register an OAuth application
Visit this page to create an application and obtain a client ID and a client secret.
User info parameters
The getUserInfo
and getUserInfoFromTokens
methods of the LinkedInProvider
accept an additional projection
parameter which is optional.
Example
const { userInfo } = await this.linkedin.getUserInfo(ctx, {
fields: [ 'id', 'firstName' ]
})
Name | Type | Description |
---|---|---|
fields | string[] | List of fields that the returned user info object should contain. Additional documentation on field projections. |
projection | string | LinkedIn projection parameter. |
Sign In and Sign Up Example
This example shows how to implement a sign in and sign up flow with sessions and a TypeORM User
entity.
user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
}
auth.controller.ts
// 3p
import {
Context,
dependency,
Get,
HttpResponseRedirect,
setSessionCookie,
} from '@foal/core';
import { GoogleProvider } from '@foal/social';
import { TypeORMStore } from '@foal/typeorm';
import { getRepository } from 'typeorm';
import { User } from '../entities';
export class AuthController {
@dependency
google: GoogleProvider;
@dependency
store: TypeORMStore;
@Get('/signin/google')
redirectToGoogle() {
return this.google.redirect();
}
@Get('/signin/google/callback')
async handleGoogleRedirection(ctx: Context) {
const { userInfo } = await this.google.getUserInfo(ctx);
let user = await getRepository(User).findOne({ email: userInfo.email });
if (!user) {
// If the user has not already signed up, then add them to the database.
user = new User();
user.email = userInfo.email;
await getRepository(User).save(user);
}
const session = await this.store.createAndSaveSessionFromUser(user);
const response = new HttpResponseRedirect('/');
setSessionCookie(response, session.getToken());
return response;
}
}
Custom Provider
If necessary, you can also implement your own provider. This one must inherit the abstract class AbstractProvider
.
Example
// 3p
import { AbstractProvider, SocialTokens } from '@foal/core';
export interface GithubAuthParameter {
// ...
}
export interface GithubUserInfoParameter {
// ...
}
export class GithubProvider extends AbstractProvider<GithubAuthParameter, GithubUserInfoParameter> {
protected configPaths = {
clientId: 'social.github.clientId',
clientSecret: 'social.github.clientSecret',
redirectUri: 'social.github.redirectUri',
};
protected authEndpoint = '...';
protected tokenEndpoint = '...';
protected userInfoEndpoint = '...'; // Optional. Depending on the provider.
protected defaultScopes: string[] = [ 'email' ]; // Optional
async getUserInfoFromTokens(tokens: SocialTokens, params?: GithubUserInfoParameter) {
// ...
}
}