OAuth2 Client
    • Dark
      Light
    • PDF

    OAuth2 Client

    • Dark
      Light
    • PDF

    Article summary

    OAuth2 is an open standard that allows applications to access user information without exposing passwords. Rocket.Chat OAuth2 Client simplifies this process by handling the OAuth2 flow with third-party services like Google, GitHub, and others directly within Rocket.Chat.

    This document demonstrates using OAuth2 to interact with Google APIs in a Rocket.Chat app.

    Prerequisites

    Ensure you have the following:

    1. Create and deploy the app: Start by creating a new Rocket.Chat app. For this example, name it OAuth, and then deploy the app to your workspace.

    2. Set up Google API credentials: In the Google API Console, create authorization credentials to obtain your Client ID and Client Secret.

    3. Configure authorized URLs: In the project’s Google API console, configure the following:

      1. Authorized JavaScript origins: Set the authorized JavaScript origins to the URL of your Rocket.Chat workspace.

      2. Authorized redirect URIs: Set the authorized redirect URIs to the app's API URL. You can find this URL by navigating to the app within your Rocket.Chat workspace, select Details, and then view the APIs section.

    OAuth2 client setup

    To set up the OAuth2 client in the Rocket.Chat app, start by importing the necessary modules into the app's main class:

    import { IConfigurationExtend } from '@rocket.chat/apps-engine/definition/accessors';
    // New files to be created in the project root folder
    import { OAuth2Service } from './OAuth2Service';
    import { OAuthCommand } from './OAuthCommand';
    
    export class OAuthApp extends App {
        private oauth2Service: OAuth2Service;
    
        protected async extendConfiguration(configuration: IConfigurationExtend): Promise<void> {
            const oauthConfig = {
                alias: 'test',
                accessTokenUri: 'https://oauth2.googleapis.com/token',
                authUri: 'https://accounts.google.com/o/oauth2/v2/auth',
                refreshTokenUri: 'https://oauth2.googleapis.com/token',
                revokeTokenUri: 'https://oauth2.googleapis.com/revoke',
                defaultScopes: ['profile', 'email'],
            };
    
            this.oauth2Service = new OAuth2Service(this, oauthConfig);
            await this.oauth2Service.setup(configuration);
    
            // Register the slash command and pass the logger
            configuration.slashCommands.provideSlashCommand(new OAuthCommand(this.oauth2Service, this.getLogger()));
        }
    }

    The code above defines the OAuth2 configuration, which includes the authorization, token endpoints, client ID, client secret, and scopes. The code also imports two files which will be created in the project root folder. These files each have two methods:

    1. OAuth2Service which manages the OAuth2 operations.

    2. OAuthCommand registers the user commands that interact with the OAuth2 service.

    OAuth service setup

    Next, create the OAuth2Service.ts file and implement the service that will handle OAuth2 operations:

    import { IOAuth2Client } from '@rocket.chat/apps-engine/definition/oauth2/IOAuth2';
    import { createOAuth2Client } from '@rocket.chat/apps-engine/definition/oauth2/OAuth2';
    import { IConfigurationExtend, IPersistence, IRead, IHttp } from '@rocket.chat/apps-engine/definition/accessors';
    import { IUser } from '@rocket.chat/apps-engine/definition/users';
    import { RocketChatAssociationRecord, RocketChatAssociationModel } from '@rocket.chat/apps-engine/definition/metadata';
    
    export class OAuth2Service {
        private oauthClient: IOAuth2Client;
    
        constructor(private app: any, private config: any) {
            this.oauthClient = createOAuth2Client(this.app, this.config);
        }
    
        public async setup(configuration: IConfigurationExtend): Promise<void> {
            try {
                await this.oauthClient.setup(configuration);
            } catch (error) {
                this.app.getLogger().error('[OAuth2Service] setup error', error);
            }
        }
    
        public async getUserAuthorizationUrl(user: IUser): Promise<string> {
            const url = await this.oauthClient.getUserAuthorizationUrl(user);
            return url.toString();
        }
    
        public async getAccessTokenForUser(user: IUser, read: IRead): Promise<any> {
            try {
                const association = new RocketChatAssociationRecord(RocketChatAssociationModel.USER, user.id);
                const [tokenData] = await read.getPersistenceReader().readByAssociation(association);
                if (tokenData) {
                    this.app.getLogger().debug(`Token data retrieved for user ${user.username}:`, tokenData);
                    this.app.getLogger().info(`Access token retrieved for user: ${user.username}`);
                    return tokenData;
                } else {
                    this.app.getLogger().warn(`No access token found for user: ${user.username}`);
                    return null;
                }
            } catch (error) {
                this.app.getLogger().error(`Failed to get access token for user: ${user.username}`, error);
                throw error;
            }
        }
        public async refreshUserAccessToken(user: IUser, persis: IPersistence): Promise<void> {
            await this.oauthClient.refreshUserAccessToken(user, persis);
        }
    
        public async revokeUserAccessToken(user: IUser, persis: IPersistence): Promise<void> {
            await this.oauthClient.revokeUserAccessToken(user, persis);
        }
    
        public async handleOAuthCallback(user: IUser, code: string, http: IHttp, persis: IPersistence): Promise<void> {
            try {
                const response = await http.post(this.config.accessTokenUri, {
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                    },
                    data: `code=${code}&client_id=${this.config.clientId}&client_secret=${this.config.clientSecret}&redirect_uri=${this.config.redirectUri}&grant_type=authorization_code`,
                });
        
                if (response.statusCode === 200 && response.data) {
                    const tokenData = response.data;
                    this.app.getLogger().debug(`Token data to be stored for user ${user.username}:`, tokenData);
                    const association = new RocketChatAssociationRecord(RocketChatAssociationModel.USER, user.id);
                    await persis.updateByAssociation(association, tokenData, true);
                    this.app.getLogger().info(`Access token stored for user: ${user.username}`);
                } else {
                    this.app.getLogger().error(`Failed to get access token: ${response.content}`);
                }
            } catch (error) {
                this.app.getLogger().error(`Failed to handle OAuth callback for user: ${user.username}`, error);
            }
        }
    }

    Here, the createOAuth2Client method takes in two parameters:

    1. app: The app itself.

    2. options: An object with props as configuration - see the definition documentation for more details.

    The setup() method configures the OAuth2Client which is used to access multiple methods like getAccessTokenForUser, revokeUserAccessToken etc., that will handle user-specific OAuth2 operations.

    • getAccessTokenForUser: Gets the token information for a specific user, if available. This receives the user instance as a parameter and returns data about the authenticated user.

    • getUserAuthorizationUrl: Returns the authorization URL to which the user must be redirected to authorize access to the application

    • refreshUserAccessToken: Refreshes the user's access token. This is useful when the user access token has expired.

    • revokeUserAccessToken: This function revokes the user's access token in the service provider. When successfully executed, users must be authenticated again before using the service.

    OAuth command setup

    To enable users to interact with the OAuth2 setup, create a new file named OAuthCommand.ts and define the following slash command:

    import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors';
    import { ISlashCommand, SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands';
    import { OAuth2Service } from './OAuth2Service';
    import { ILogger } from '@rocket.chat/apps-engine/definition/accessors';
    
    export class OAuthCommand implements ISlashCommand {
        public command = 'oauth';
        public i18nParamsExample = '';
        public i18nDescription = 'OAuth command for testing';
        public providesPreview = false;
    
        constructor(private readonly oauth2Service: OAuth2Service, private readonly logger: ILogger) {}
    
        public async executor(context: SlashCommandContext, read: IRead, modify: IModify, http: IHttp, persis: IPersistence): Promise<void> {
            const user = context.getSender();
            const args = context.getArguments();
        
            try {
                if (args[0] === 'token') {
                    // Retrieve and display the access token
                    const tokenData = await this.oauth2Service.getAccessTokenForUser(user, read);
                    if (tokenData && tokenData.token) {
                        await modify.getNotifier().notifyUser(
                            user,
                            modify.getCreator().startMessage()
                                .setText(`Access token: ${tokenData.token}`)
                                .setRoom(context.getRoom())
                                .getMessage()
                        );
                    } else {
                        await modify.getNotifier().notifyUser(
                            user,
                            modify.getCreator().startMessage()
                                .setText('No access token found. Please authorize the app first.')
                                .setRoom(context.getRoom())
                                .getMessage()
                        );
                    }
                } else if (args[0] === 'refresh') {
                    await this.oauth2Service.refreshUserAccessToken(user, persis);
                    await modify.getNotifier().notifyUser(
                        user,
                        modify.getCreator().startMessage()
                            .setText('Access token refreshed successfully.')
                            .setRoom(context.getRoom())
                            .getMessage()
                    );
                } else if (args[0] === 'revoke') {
                    await this.oauth2Service.revokeUserAccessToken(user, persis);
                    await modify.getNotifier().notifyUser(
                        user,
                        modify.getCreator().startMessage()
                            .setText('Access token revoked successfully.')
                            .setRoom(context.getRoom())
                            .getMessage()
                    );
                } else {
                    const authUrl = await this.oauth2Service.getUserAuthorizationUrl(user);
                    await modify.getNotifier().notifyUser(
                        user,
                        modify.getCreator().startMessage()
                            .setText(`Please authorize the app by visiting: ${authUrl}`)
                            .setRoom(context.getRoom())
                            .getMessage()
                    );
                }
            } catch (error) {
                this.logger.error('Error executing OAuth command:', error);
                await modify.getNotifier().notifyUser(
                    user,
                    modify.getCreator().startMessage()
                        .setText(`An error occurred while processing the OAuth command: ${error.message}`)
                        .setRoom(context.getRoom())
                        .getMessage()
                );
            }
        }
    }

    The slash command here is oauth which accepts different arguments (token, refresh, revoke) to perform corresponding actions.

    To see this app in action, deploy the app and try using these slash commands in the workspace:

    • /oauth to authorize the app.

    • /oauth token to get the authorization token.

    • /oauth refesh to refresh the token.

    • /oauth revoke to revoke the access.

    When the application is successfully authorized, you can verify the app through the third-party apps connected to your Google account. Likewise, when you revoke this access, the app should be subsequently removed.


    Was this article helpful?

    What's Next
    Changing your password will log you out immediately. Use the new password to log back in.
    First name must have atleast 2 characters. Numbers and special characters are not allowed.
    Last name must have atleast 1 characters. Numbers and special characters are not allowed.
    Enter a valid email
    Enter a valid password
    Your profile has been successfully updated.
    ESC

    Eddy AI, facilitating knowledge discovery through conversational intelligence