OAuth Authentication
The OAuth plugin enables OAuth2 sign-in and connected external accounts in AdminForth. Users can sign in with Google, GitHub, Facebook, Telegram, or another OAuth2 provider. Logged-in users can also connect additional provider accounts from Settings -> Connected Accounts.
OAuth should be configured with an external identities resource. That resource stores stable provider identity data (provider, subject) and optional profile fields. Agent chat surfaces use the same connected accounts to resolve Telegram, Slack, Teams, or other external chat users.
Using OAuth without externalIdentityResource is deprecated. Configure an external identities resource for new projects.
Installation
Install the plugin and at least one OAuth adapter:
pnpm install @adminforth/oauth --save
pnpm install @adminforth/oauth-adapter-google --save
If you want Agent chat surfaces, install the OAuth adapter for the same provider as the chat surface. For example, Telegram chat surface support requires both:
pnpm install @adminforth/chat-surface-adapter-telegram --save
pnpm install @adminforth/oauth-adapter-telegram --save
External Identities Resource
Create a resource that stores connected OAuth accounts. The default field names are:
adminUserId— links the external identity to the AdminForth userprovider— adapter class name, for exampleAdminForthAdapterGoogleOauth2subject— stable provider account idexternalUserId— chat-specific user id, such as Telegram ID, Slack ID, or Teams ID
The defaults can be overridden in plugin options when your schema uses different field names.
Migration example (Prisma)
If you're using Prisma, add the model to your schema.prisma:
model AdminUserExternalIdentity {
id String @id @default(uuid())
adminUserId String
provider String
subject String
externalUserId String?
email String?
phone String?
fullName String?
avatarUrl String?
meta Json?
createdAt DateTime @default(now())
@@unique([provider, subject])
@@index([adminUserId])
}
Then create and apply the migration (example commands from dev-demo):
pnpm makemigration --name add-admin-user-external-identities ; pnpm migrate:local
import { AdminForthDataTypes, type AdminForthResourceInput } from 'adminforth';
import { randomUUID } from 'crypto';
export default {
dataSource: 'maindb',
table: 'AdminUserExternalIdentity',
resourceId: 'admin_user_external_identities',
label: 'Admin User External Identities',
columns: [
{
name: 'id',
type: AdminForthDataTypes.STRING,
primaryKey: true,
fillOnCreate: () => randomUUID(),
showIn: { create: false, edit: false },
},
{ name: 'adminUserId', type: AdminForthDataTypes.STRING, required: true },
{ name: 'provider', type: AdminForthDataTypes.STRING, required: true },
{ name: 'subject', type: AdminForthDataTypes.STRING, required: true },
{ name: 'externalUserId', type: AdminForthDataTypes.STRING },
{ name: 'email', type: AdminForthDataTypes.STRING },
{ name: 'phone', type: AdminForthDataTypes.STRING },
{ name: 'fullName', type: AdminForthDataTypes.STRING },
{ name: 'avatarUrl', type: AdminForthDataTypes.STRING },
{ name: 'meta', type: AdminForthDataTypes.JSON },
{
name: 'createdAt',
type: AdminForthDataTypes.DATETIME,
fillOnCreate: () => new Date().toISOString(),
showIn: { create: false, edit: false },
},
],
} satisfies AdminForthResourceInput;
Configuration
This example configures Google OAuth. See OAuth2 Providers for provider setup details.
import OAuthPlugin from '@adminforth/oauth';
import AdminForthAdapterGoogleOauth2 from '@adminforth/oauth-adapter-google';
plugins: [
new OAuthPlugin({
emailField: 'email',
externalIdentityResource: {
resourceId: 'admin_user_external_identities',
emailField: 'email',
phoneField: 'phone',
fullNameField: 'fullName',
avatarUrlField: 'avatarUrl',
metaField: 'meta',
},
adapters: [
new AdminForthAdapterGoogleOauth2({
clientID: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
}),
],
}),
]
adminUserIdField, providerField, subjectField, and externalUserIdField default to adminUserId, provider, subject, and externalUserId. Configure them only when your resource uses different field names.
Google Provider Setup
- Go to the Google Cloud Console and log in.
- Create a new project or select an existing one.
- Go to
APIs & Services->Credentials. - Create credentials for OAuth 2.0 client IDs.
- Select application type: "Web application".
- Add your application's name and redirect URI.
- In "Authorized redirect URIs", add
https://your-domain/oauth/callbackandhttp://localhost:3500/oauth/callback. IncludebaseUrlwhen your AdminForth app uses it, for examplehttps://your-domain/base/oauth/callback. - Add the credentials to your
.envfile:
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
Connected Accounts
When externalIdentityResource is configured, AdminForth adds Connected Accounts to the user settings menu. A logged-in user can connect additional external accounts there. This is also how Agent chat surfaces identify external users: for example, a Telegram bot message is matched to the AdminForth user through the Telegram OAuth identity stored in the external identities resource.
Email Confirmation
The plugin supports automatic email confirmation for OAuth users. To enable this:
- Add the
email_confirmedfield to your database schema:
model adminuser {
// ... existing fields ...
email_confirmed Boolean @default(false)
}
- Run the migration:
pnpm makemigration --name add-email-confirmed-to-adminuser ; pnpm migrate:local
- Configure the plugin with
emailConfirmedField:
new OAuthPlugin({
// ... adapters configuration ...
emailField: 'email',
emailConfirmedField: 'email_confirmed' // Enable email confirmation tracking
}),
When using OAuth:
- New users will have their email automatically confirmed (
email_confirmed = true) - Existing users will have their email marked as confirmed upon successful OAuth login
- The
email_confirmedfield must be a boolean type
Open Signup
By default, users must exist in the system before they can log in with OAuth. You can enable automatic user creation for new OAuth users with the openSignup option:
new OAuthPlugin({
// ... adapters configuration ...
openSignup: {
enabled: true,
defaultFieldValues: { // Set default values for new users
role: 'user',
},
},
}),
UI Customization
You can customize the UI of the OAuth login buttons by using the iconOnly and pill options.
new OAuthPlugin({
// ... adapters configuration ...
iconOnly: true, // Show only provider icons without text
pill: true, // Use pill-shaped buttons instead of rectangular
}),
OAuth2 Providers
Facebook Adapter
Install Adapter:
pnpm install @adminforth/oauth-adapter-facebook --save
- Go to the Facebook Developers
- Go to
My apps - Create a new project or select an existing one (choose Authenticate and request data from users with Facebook Login)
- Go to
Use Cases-Authenticate and request data from users with Facebook Login->Customizeand add email permissions - Go to
App Settings->Basic - Get App ID and App secret
- Add the credentials to your
.envfile:
FACEBOOK_CLIENT_ID=your_facebook_client_id
FACEBOOK_CLIENT_SECRET=your_facebook_client_secret
Add the adapter to your plugin configuration:
import AdminForthAdapterFacebookOauth2 from '@adminforth/oauth-adapter-facebook';
// ... existing resource configuration ...
plugins: [
new OAuthPlugin({
adapters: [
...
new AdminForthAdapte# OAuth AuthenticationrFacebookOauth2({
clientID: process.env.FACEBOOK_CLIENT_ID,
clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
}),
],
}),
]
GitHub Adapter
Install Adapter:
pnpm install @adminforth/oauth-adapter-github --save
- Go to the GitHub Apps
- Create a new app or select an existing one
- Go to the
Permisiions & events->Account permissions->Email addressesand change toRead-only - Go to the
Generaland click toGenerate a new client secretbutton and copy secret - Add the credentials to your
.envfile:
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
Add the adapter to your plugin configuration:
import AdminForthAdapterGithubOauth2 from '@adminforth/oauth-adapter-github';
// ... existing resource configuration ...
plugins: [
new OAuthPlugin({
adapters: [
...
new AdminForthAdapterGithubOauth2({
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
],
}),
]
Telegram Adapter
Install Adapter:
pnpm install @adminforth/oauth-adapter-telegram --save
Create Telegram OpenID Connect credentials in BotFather:
- Open BotFather.
- Click Open and open it in the Telegram app.
- Select or Create your bot.
- Open Login Widget.
- Click Switch to OpenID Connect Login.
- Copy the client ID and client secret shown at the top.
- Set Redirect URI to
https://example.com/oauth/callback.
Add the credentials to your .env file:
TELEGRAM_CLIENT_ID=your_telegram_client_id
TELEGRAM_CLIENT_SECRET=your_telegram_client_secret
Add the adapter to your plugin configuration:
import AdminForthAdapterTelegramOauth2 from '@adminforth/oauth-adapter-telegram';
// ... existing resource configuration ...
plugins: [
new OAuthPlugin({
adapters: [
...
new TelegramOauthAdapter({
clientID: process.env.TELEGRAM_CLIENT_ID as string,
clientSecret: process.env.TELEGRAM_CLIENT_SECRET as string,
redirectUri: 'https://example.com/oauth/callback',
scopes: ['openid', 'profile', 'phone'],
}),
],
}),
]
Register the same redirectUri in BotFather under Login Widget → Redirect URI.
If you use @adminforth/chat-surface-adapter-telegram, users must connect Telegram from Settings → Connected Accounts before the Telegram bot can identify them.
Kaycloack Adapter
Install Adapter:
pnpm install @adminforth/oauth-adapter-keycloak --save
If you need a basic Keycloak setup which tested with AdminForth, you can follow this minimal KeyClock setup example.
- Update your
.envfile with the following Keycloak configuration:
KEYCLOAK_CLIENT_ID=your_keycloak_client_id
KEYCLOAK_CLIENT_SECRET=your_keycloak_client_secret
KEYCLOAK_URL=http://localhost:8080
KEYCLOAK_REALM=your_keycloak_realm
- Add the adapter to your plugin configuration:
import AdminForthAdapterKeycloakOauth2 from '@adminforth/oauth-adapter-keycloak';
// ... existing resource configuration ...
plugins: [
new OAuthPlugin({
adapters: [
...
new AdminForthAdapterKeycloakOauth2({
name: "Keycloak",
clientID: process.env.KEYCLOAK_CLIENT_ID,
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
keycloakUrl: process.env.KEYCLOAK_URL,
realm: process.env.KEYCLOAK_REALM,
useOpenIdConnect: true,
}),
],
}),
]
Microsoft Adapter
Install Adapter:
pnpm install @adminforth/oauth-adapter-microsoft --save
- In the Microsoft Azure Portal, search for and click App registrations, then
New registration. - Give your application a name. This name will be visible to your users.
- Set the audience for the app to
Accounts in my organizational directory and personal Microsoft accounts. This allows your user to log in using any Microsoft account. - Set the Redirect URI platform to Web and enter your project's redirect URI.
- Go to your app and search
API permissions, clickAdd a permission, selectMicrosoft Graph, selectDelegated permissions, enable permissions:offline_access,openid,profile,User.Read, clickAdd permissions. - Serch
Certificates & secrets, clickNew client secretand create client secret. - Get Application ID and Client Secret.
- Add the credentials to your
.envfile:
MICROSOFT_CLIENT_ID=your_application_id
MICROSOFT_CLIENT_SECRET=your_microsoft_client_secret
Add the adapter to your plugin configuration:
import AdminForthAdapterMicrosoftOauth2 from '@adminforth/oauth-adapter-microsoft';
// ... existing resource configuration ...
plugins: [
new OAuthPlugin({
adapters: [
...
new AdminForthAdapterMicrosoftOauth2({
clientID: process.env.MICROSOFT_CLIENT_ID,
clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
useOpenIdConnect: true,
}),
],
}),
]
Twitch Adapter
Install Adapter:
pnpm install @adminforth/oauth-adapter-twitch --save
- Go to the Twitch dashboard
- Create a new app or select an existing one
- In
OAuth Redirect URLsaddhttps://your-domain/oauth/callback(http://localhost:3500/oauth/callback) - Go to the app and copy
Client ID, click toGenerate a new client secret(in Twitch this button can be used only once for some time, becouse of this dont lose it) button and copy secret . - Add the credentials to your
.envfile:
TWITCH_CLIENT_ID=your_twitch_client_id
TWITCH_CLIENT_SECRET=your_twitch_client_secret
Add the adapter to your plugin configuration:
import AdminForthAdapterTwitchOauth2 from '@adminforth/oauth-adapter-twitch';
// ... existing resource configuration ...
plugins: [
new OAuthPlugin({
adapters: [
...
new AdminForthAdapterTwitchOauth2({
clientID: process.env.TWITCH_CLIENT_ID,
clientSecret: process.env.TWITCH_CLIENT_SECRET,
}),
],
}),
]
Need custom provider?
Just fork any existing adapter e.g. Google and adjust it to your needs.
This is really easy, you have to change less then 10 lines of code in this file
Then just publish it to npm and install it in your project.
Links to adapters: Google GitHub Facebook Keycloak
Fill user full name
If you have a fullName field in your users resource, you can add it to the plugin setup:
plugins: [
...
new OAuthPlugin({
...
userFullNameField: 'fullName'
...
}),
]
This field will be automatically filled with the name that the provider returns, if this field was empty.
☝️Not all providers return full name or even if they do, there is no guarantee that they will be correct
Google Adapter: returns fullName, but if there is no last name - it will return only first name
Facebook: returns fullName
Github: returns name or fullName (depends of what user added in name field)
Keycloak: returns fullName
Microsoft: returns fullName
Twitch: return only users display name
Automatically save users avatar
If you want to automatically upload users avatar from the provider, you can use userAvatarField prop.
Example:
First of all you'll need to create avatar field in your database:
Update schema prisma file:
model adminuser {
id String @id
email String @unique
password_hash String
role String
created_at DateTime
avatar String
}
And make migration:
pnpm makemigration --name add-avatar-field ; pnpm migrate:local
Then add this field to users resource and install upload plugin:
import UploadPlugin from '@adminforth/upload';
...
columns: [
...
{
name: "avatar",
type: AdminForthDataTypes.STRING,
showIn: {
list: true,
show: true
},
},
...
],
...
plugins: [
...
new UploadPlugin({
pathColumnName: "avatar",
storageAdapter: new AdminForthAdapterS3Storage({
bucket: process.env.AWS_BUCKET_NAME,
region: process.env.AWS_REGION,
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
}),
allowedFileExtensions: [
"jpg",
"jpeg",
"png",
"gif",
"webm",
"exe",
"webp",
],
maxFileSize: 1024 * 1024 * 20, // 20MB
filePath: ({ originalFilename, originalExtension, contentType, record }) => {
return `aparts/${new Date().getFullYear()}/${originalFilename}.${originalExtension}`
},
preview: {
maxWidth: "200px",
},
}),
...
]
...
Then update your plugin setup:
...
plugins: [
...
new OAuthPlugin({
userAvatarField: "avatar",
...
})
...
]
...
And finally add this callback:
auth: {
...
avatarUrl: async (adminUser)=>{
const plugin = admin.getPluginsByClassName('UploadPlugin').find(p => p.pluginOptions.pathColumnName === 'avatar') as any;
if (!plugin) {
throw new Error('Upload plugin for avatar not found');
}
if (adminUser.dbUser.avatar === null || adminUser.dbUser.avatar === undefined || adminUser.dbUser.avatar === '') {
return undefined;
}
const imageUrl = await plugin.getFileDownloadUrl(adminUser.dbUser.avatar || '', 3600);
return imageUrl;
},
...
}