Skip to main content
Version: Canary 🚧

RocketChat Oauth flow

RocketChat Oauth overview​

RocketChatServer Oauth configuration​

To create a custom authentication method in your Rocket.Chat workspace:

  • Navigate to Administration > Workspace > Settings > OAuth.

  • Click on Custom OAuth and set the new OAuth integration name.

  • After creating the new integration, you are given the Callback URL. Provide this as the redirect URI when you are creating your Azure app.

  • You will also need to fill in other configuration fields.

For more detialed info please see the Auth with Microsoft code flow

Home page request and response​

Rocket.Chat offers integrations with various providers such as Google, Apple, and GitHub. This provides flexibility and security so users can log in with any of these credentials. All the type of login method will loaded in the home page. Authorize path, Redirect uri, Response type and other import loaded info which is very useful for login.

Generate credentialToken and request auth code​

Generate credentialToken​

After click the Oauth login button, the credentialToken which like session id for login will generated in the random way, so only the user and current client(browser) know the session id. Then the loginUrl composed with authorizePath, redirect_uri, response_type and state will be as request to author server.

info

The credentialToken and state is key info for login. The state is base64 type which encoded from loginStyle, credentialToken, and redirectUrl. And the state is import query parameter to redirect back to RocketServer. It will be used to retry the credentialToken and credentialSecret info for login. There are two type of loign style redirect and popup.

METEOR/packages/oauth/oauth_client.js
OAuth._stateParam = (loginStyle, credentialToken, redirectUrl) => {
const state = {
loginStyle,
credentialToken,
isCordova: Meteor.isCordova
};

if (loginStyle === 'redirect' ||
(Meteor.settings?.public?.packages?.oauth?.setRedirectUrlWhenLoginStyleIsPopup && loginStyle === 'popup')
) {
state.redirectUrl = redirectUrl || ('' + window.location);
}

return Base64.encode(JSON.stringify(state));
};
Rocket.Chat/apps/meteor/app/custom-oauth/client/CustomOAuth.ts

async requestCredential(
options: Meteor.LoginWithExternalServiceOptions = {},
credentialRequestCompleteCallback: (credentialTokenOrError?: string | Error) => void,
) {
const config = await loginServices.loadLoginService<OAuthConfiguration>(this.name);
if (!config) {
if (credentialRequestCompleteCallback) {
credentialRequestCompleteCallback(new Accounts.ConfigError());
}
return;
}

const credentialToken = Random.secret();
const loginStyle = OAuth._loginStyle(this.name, config);

const separator = this.authorizePath.indexOf('?') !== -1 ? '&' : '?';

const loginUrl =
`${this.authorizePath}${separator}client_id=${config.clientId}&redirect_uri=${encodeURIComponent(
OAuth._redirectUri(this.name, config),
)}&response_type=${encodeURIComponent(this.responseType)}` +
`&state=${encodeURIComponent(OAuth._stateParam(loginStyle, credentialToken, options.redirectUrl))}&scope=${encodeURIComponent(
this.scope,
)}`;

OAuth.launchLogin({
loginService: this.name,
loginStyle,
loginUrl,
credentialRequestCompleteCallback,
credentialToken,
popupOptions: {
width: 900,
height: 450,
},
});
}

LoginUrl
https://login.microsoftonline.com/d5e685a9-3e23-42d9-9efa-15cc5c1ce7db/oauth2/authorize?
client_id=e3845613-0331-49c0-9f11-fb6a634242d2&
redirect_uri=https%3A%2F%2F212c-34-92-204-228.ngrok-free.app%2F_oauth%2F365oauth&
response_type=code&
state=eyJsb2dpblN0eWxlIjoicmVkaXJlY3Q1LCJjcmVkZW50aWFsV39rZW4iOiJyMHk4Vk54ZlN4MUhmY0lFZXVLbnQ1dnAzNW1aWHM2cHdWTkhzcWRNb1BvIiwiaXNDb3Jkb3ZhIjpmYWxzZSwicmVkaXJlY3RVcmwiOiJodHRwczovLzIxMmMtMzQtOTItMjA0LTIyOC5uf3Jvay1gcmVlLmFwcC9ob21lIn0%3D&
scope=openid%20profile%20email

Request auth code​

The auth code will be the response of LoginUrl request.

Code
code=0.AbcAqYXm1SM-2UKe-hXMXBzn2x4WhOMxA8BJnxH7amNCQtL8APU.AgABBAIAAAApTwJmzXqdR4BN2miheQMYAgDs_wUA9P8Hjlb9sDQamfLxWl_ukGw1Js8IBAlNSq9K3y6MFvkr-uCDOasVt1WLipwEY2irrnDPJVI78EONQ8P1jdKlIDjt-5sQME63u654FbJGKq8H-AV6YrDtaYxfV7cC0YGEcVlbDwYzT0-NRlHW17I9OXQmF0wyg0FIPqK8EJa-zCKPkGTGwqsPh11Ul5-qv90TxMjsfZPd9KZs2Kixiigtx_otW_S6qhbFHCXQVeSG_sO9W0Vn2avdOiAktEILUOWQo-ZH-wmSxfEzibkjvCZYXEtccP1kDxUhQa5Ck4zQWReIjqf2atH3_74ZhFFGMReKSVujqfO46hA7gd4OY7VVGikGlloUWVSFboMXQvQ1GKi7EJ2DEOiIXVM3Uu4_sl3X4dKShcBjSy4kuHfb8MMpQuUhL4NFmBg7A-qVl4D1muhayPaJyuT2-prggC5ciL7rlgMAWcoGgU5dpC-WujQRQVbva1w1HyGI6QL8biiU_fP0wP6-0mAsNVsBDhuMtoh0VZ2eKbLWGyvLL0pgTh8XiUMte9t5JgL4dSzlVAcHXUK2HWBeHql2J0LsR73arQHMh5QhlRa94JGB1zNtFnXn5xjfvkYDF3zTuxIwJa4JoEtPu9Krnu6-rhA3sHVUNUE6uTBDoIz5udVmAyaa4hNwDEhajSk4qCvEvEgk&

Return code and redirect_uri for callback​

Because the LoginUrl contain the redirect_uri as parameter, the Oauth2 Authorize server will return redirect_uri and code for callback to RocketChatServer. In the Oauth2 Authorize server, redirect_uri will be checked weather it is equal to one of the uris in the server.

Redirect to redirect_uri with code and state​

Redirect means to access the redirect_uri is not from RokcetChatClient directly. It is a passive behavior on the browser side. After getting the code info and loading new page from the LoginUrl, the page will direct to redirect_uri automatically, so this request is not issued from RokcetChatClient, but passive behavior on the browser.

Return credentialToken and credentialSecret​

In the following, the redirect_uri with code, state, and session_state as parameter to obtain credentialToken and credentialSecret from RocketChatServer. The credentialToken and credentialSecret is parameter for login.

redirect_uri
https://212c-34-92-204-228.ngrok-free.app/_oauth/365oauth?
code=0.AbcAqYXm1SM-2UKe-hXMXBzn2x4WhOMxA8BJnxH7amNCQtL8APU.AgABBAIAAAApTwJmzXqdR4BN2miheQMYAgDs_wUA9P8Hjlb9sDQamfLxWl_ukGw1Js8IBAlNSq9K3y6MFvkr-uCDOasVt1WLipwEY2irrnDPJVI78EONQ8P1jdKlIDjt-5sQME63u654FbJGKq8H-AV6YrDtaYxfV7cC0YGEcVlbDwYzT0-NRlHW17I9OXQmF0wyg0FIPqK8EJa-zCKPkGTGwqsPh11Ul5-qv90TxMjsfZPd9KZs2Kixiigtx_otW_S6qhbFHCXQVeSG_sO9W0Vn2avdOiAktEILUOWQo-ZH-wmSxfEzibkjvCZYXEtccP1kDxUhQa5Ck4zQWReIjqf2atH3_74ZhFFGMReKSVujqfO46hA7gd4OY7VVGikGlloUWVSFboMXQvQ1GKi7EJ2DEOiIXVM3Uu4_sl3X4dKShcBjSy4kuHfb8MMpQuUhL4NFmBg7A-qVl4D1muhayPaJyuT2-prggC5ciL7rlgMAWcoGgU5dpC-WujQRQVbva1w1HyGI6QL8biiU_fP0wP6-0mAsNVsBDhuMtoh0VZ2eKbLWGyvLL0pgTh8XiUMte9t5JgL4dSzlVAcHXUK2HWBeHql2J0LsR73arQHMh5QhlRa94JGB1zNtFnXn5xjfvkYDF3zTuxIwJa4JoEtPu9Krnu6-rhA3sHVUNUE6uTBDoIz5udVmAyaa4hNwDEhajSk4qCvEvEgk&
state=eyJsb2dpblN0eWxlIjoicmVkaXWERY3QiLCJjcmVkZW50aWFsVG9rZW4iOiJwVjNOcHdRUnlLc21HbUxwc3EtRHlHWjRmeGVwYWl6TXhPRWZmQVNiWmVPIiwiaXNDb3Jkb3ZhIjpmYWxzZSwicmVkaXJlY3RVcmwiOiJodHRwczovLzIxMmMtMzQtOTItMjA0LTIyOC5uZ3Jvay1mcmVlLmFwcC9ob21lIn0%3d&
session_state=2e1ade24-bffe-46ef-b1c7-934dc8dfbd00#

Request Oauth token by authorization code​

info

The code, state, and session_state will be the query parameter in the registerService. In this funciton, the RocketServer will getAccessToken with code then getIdentity with access_token. So after registerService function execute, the RocketServer will have all the infomation about indentity include name, email, access_token, refres_token and the others. Let bread down step by step in the .

registerService query
custom_oauth_server registerService query 05 {
code: '0.AbcAqYXm1SM-2UKe-hXMXBzn2xNWhOMxA8BJnxH7amNCQtL8APU.AgABBAIAAAApTwJmzXqdR4BN2miheQMYAgDs_wUA9P9DwxS3im8tRSDQdtHxq88XUJxfHdm8qXE_sPaboChfsUSBiWTOOWk5wi3gGT_zILXoafj7-Ae9DUsG9gBPYFk4HXNykWKEhJWyHGeXjR1LVwyZV16w9udhzhZFYpSaTg-jEPcv5sFzbgoxviDNcxcIQTzhmrV2C-SfBCV4-qOZJ4lIcP6eAFm1AexMrnpO5fnI2k-mnZS43E3PiNIQsqPQrItEx1miMNxqAshwQ1QIJaCnzUtus6zd4d6cZma08zQy65wn0axWE2TEXleqOoV81t1-hapdyd-ROpobuYk6ImKWI2Aw3hnjJqWCfJvgkxDLk0ycFuRcdUZRUug8DQtypJk7lrG13NJ7Cx2dbAWNTxLT4kwnvYGjLSLqTk9YQCdwmFgcYsGLk4cqlHTCIg4BJ4ETUxOjYlk2TVmD6vgA3MOZSMREwqyyh4s8iZg9bC2qGQkRL26FNlaiBHRMOGMmptBDo7EONp5RGRMz5A2Wnzepx4w3YRtJUfgYdALRNM6d4lh0l22YUyuFja6HFIS4QIRrHjbOHXyIC2w1_nCfcBPfpw4h75Fmf2iJFvmBeRNdlmU3BMCCnZ2rghkfTSohqzwe2HgBNGKo6pmW6o8WONTDnsIaWtEB4aPsBqL2NX5rOEnQMF2yBsNh_KMhziGjJkQy4ikl1mkAFQ',
state: 'eyJsb2dpblN0eWxlIjoicmVkaXJlY3QiLCJjcmVkZW50aWFsVG9rZW4iOiJBNnpMMXZEQnNaTkZMTGw0YzE4bW5YQXJsTU9MUU1UMDJyWXpWbkxfcGp0IiwiaXNDb3Jkb3ZhIjpmYWxzZSwicmVkaXJlY3RVcmwiOiJodHRwczovLzIxMmMtMzQtOTItMjA0LTIyOC5uZ3Jvay1mcmVlLmFwcC9ob21lIn0=',
session_state: '2e1ade24-bffe-46ef-b1c7-934dc8dfbd00'
}
Rocket.Chat/apps/meteor/app/custom-oauth/server/custom_oauth_server.js
registerService() {
const self = this;
OAuth.registerService(this.name, 2, null, async (query) => {
console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server registerService query 05", query);
const response = await self.getAccessToken(query);
const identity = await self.getIdentity(response.access_token, query);
const serviceData = {
_OAuthCustom: true,
serverURL: self.serverURL,
accessToken: response.access_token,
idToken: response.id_token,
expiresAt: +new Date() + 1000 * parseInt(response.expires_in, 10),
};

// only set the token in serviceData if it's there. this ensures
// that we don't lose old ones (since we only get this on the first
// log in attempt)
if (response.refresh_token) {
serviceData.refreshToken = response.refresh_token;
}

_.extend(serviceData, identity);

const data = {
serviceData,
options: {
profile: {
name: identity.name,
},
},
};

//logger.debug({ msg: 'RegisterService Data', response });
console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server registerService data 05", data);

return data;
});
}

The RocketChat can use the authorization code to request an access token for the target resource. Authorization codes are short lived. Typically, they expire after about 10 minutes.

	async getAccessToken(query) {
const config = await ServiceConfiguration.configurations.findOneAsync({ service: this.name });
if (!config) {
throw new Accounts.ConfigError();
}

let response = undefined;

const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': this.userAgent, // http://doc.gitlab.com/ce/api/users.html#Current-user
'Accept': 'application/json',
};
const params = new URLSearchParams({
code: query.code,
redirect_uri: OAuth._redirectUri(this.name, config),
grant_type: 'authorization_code',
state: query.state,
});

// Only send clientID / secret once on header or payload.
if (this.tokenSentVia === 'header') {
const b64 = Buffer.from(`${config.clientId}:${OAuth.openSecret(config.secret)}`).toString('base64');
headers.Authorization = `Basic ${b64}`;
} else {
params.append('client_secret', config.secret);
params.append('client_id', config.clientId);
}

try {
const request = await fetch(`${this.tokenPath}`, {
method: 'POST',
headers,
body: params,
});

if (!request.ok) {
throw new Error(request.statusText);
}

response = await request.json();

console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server getAccessToken 03", response);

} catch (err) {
const error = new Error(`Failed to complete OAuth handshake with ${this.name} at ${this.tokenPath}. ${err.message}`);
throw _.extend(error, { response: err.response });
}

if (response.error) {
// if the http response was a json object with an error attribute
throw new Error(`Failed to complete OAuth handshake with ${this.name} at ${this.tokenPath}. ${response.error}`);
} else {
return response;
}
}

Return an access_token and a refresh_token​

Token info
custom_oauth_server getAccessToken 03 {
token_type: 'Bearer',
expires_in: '3599',
ext_expires_in: '3599',
expires_on: '1719714834',
access_token: 'PAQABAQIAAAApTwJmzXqdR4BN2miheQMYlbd5ZfbyIQUX1iW0FWGmFGvyjTFuy2zHZ1PoDvLgz0D6hAas62MopvXHFIhX53amdiH8_8t-SAGJsjr--GeBeY4Xa3CBx4-1Wg6KutRkg6nZPb-tOO-JSvdlEwlJ_QqPbv_MKZjoqiDbbK0AwbeiQmQm7uTxfk_EjiXxJGM5nYGoHNeg6rslqYYROsjEw2mFf3kyYYFEbUstPfc6Mc_4t3I8QJTNfC0NELnnNHP1b3khDyXtWEeYgcG4v9bNu3_s8MuUJmF1Cv1oWG8p7uTAmpmt84aQ5lnkO3fcxH4yLOH_CPcYLa8JU4K6EaqW7qq8rlvFCTQk0EzWDfhDjfSXESY8zylhFyOqRKsuBVlUiQ7FU42ukPbNDtdJEcwgOm1m9KIFEU5EDUB7ljyPwF_nwgXHnhF6tsU0XEFR3hCAsp0_TgGvtE5xR7mNHC59ZrY86QpcgTHC3lXyDj58VGgZTiAA',
refresh_token: '0.AbcAqYXm1SM-2UKe-hXMXBzn2xNWhOMxA8BJnxH7amNCQtL8APU.AgABAwEAAAApTwJmzXqdR4BN2miheQMYAgDs_wUA9P_RBLQedJRbR8Ll3VPCsjY6zuGnnG6AaZjrFXJGr7hxvjrMD8Azvdxc2BPjThB6qx9JVac8azajaKio_azzrov5EgHgPxCoX5tIL4mV-Yg98cVi5Xm8kOrCjUupc6l0qXRec5Eq69VHJwIUyJrsxpPc8LsHYwDNoKhPWm7fBVKUW0g5ifxg_RBAF43MJYrC5z65tgBRmXwsRJ7ZgDBqnXelYWLKgd-3HG5MkuRpv3JB8vxGRSS5kKdEExU0VRXJ08MMVNHIX_bdzFKyf8LYB2V1sU7gxnWnTmDgQAwvS0Asp0XE-nPY9JtUs_NkgsWD_JQEXv40ib3kOXWNf07ZnVD0DC4E-qE2G76CoEq0l0OWjH9EHf_xhLr21skTWRnaRnnQ8UN1dpyNrf5ymSkhxvTXNOCA6xZUV_ZhjZmjPixig0-8Qx8xDV4RTqnM2NiV8KiR9sL156j31PIeGEp5cagk_lbBqxU2XwGJ9hKI5JbkN3Jg_YVltVKpfkdv4hYzlD94hkepWngLi7q5Sn-Zv-LAC4F8WguDTPt6GjTtOCTG3Yl6BuiMDiBy4-xYM_5BhTdJ1ZCF8wAm-o86U1F1bWTGMa7KQksPsb6AGFGpRcAhbxfki6lETARM18DMVaogACUASffA0ppkSAKoRofJ9-EvXWqodItnTtFmW9WurbNjd0otb1BRP_EIJqKDjd4IAgwbNT0h2C4rWUMEji6ET1KgHun7AGAUTF2EnawYpEzYomCR-pM',
id_token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1HTHFqOThWTkxvWGFGZnBKQ0JwZ0I0SmFLcyIsImtpZCI6Ik1HTHFqOThWTkxvWGFGZnBKQ0JwZ0I0SmFLcyJ9.eyJhdWQiOiJlMzg0NTYxMy0wMzMxLTQ5YzAtOWYxMS1mYjZhNjM0MjQyZDIiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9kNWU2ODVhOS0zZTIzLTQyZDktOWVmYS0xNWNjNWMxY2U3ZGIvIiwiaWF0IjoxNzE5NzEwOTM0LCJuYmYiOjE3MTk3MTA5MzQsImV4cCI6MTcxOTcxNDgzNCwiYW1yIjpbInB3ZCIsIm1mYSJdLCJmYW1pbHlfbmFtZSI6Ikd1bXAiLCJnaXZlbl9uYW1lIjoiRm9ycmVzdCIsImlwYWRkciI6IjIyMy4xMDQuNDIuODciLCJuYW1lIjoiRm9ycmVzdCBHdW1wIiwib2lkIjoiOTBmNzU5NmItODhiNi00NzY4LTgyMDQtOGM0NzZhNzNmZTI1IiwicmgiOiIwLkFiY0FxWVhtMVNNLTJVS2UtaFhNWEJ6bjJ4TldoT014QThCSm54SDdhbU5DUXRMOEFQVS4iLCJzdWIiOiJLR2NlTEgtSG9FTklONUg0alZ3d2FMSTJydHBILVMyQ1RuSkNIQTB5MGFrIiwidGlkIjoiZDVlNjg1YTktM2UyMy00MmQ5LTllZmEtMTVjYzVjMWNlN2RiIiwidW5pcXVlX25hbWUiOiJGb3JyZXN0R3VtcEBHaXRjb2lucy5vbm1pY3Jvc29mdC5jb20iLCJ1cG4iOiJGb3JyZXN0R3VtcEBHaXRjb2lucy5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiJGSkI0a0xDRnlVV21ud1ZvdE93OEFRIiwidmVyIjoiMS4wIn0.oGP8qczVpNjzG03en1LmF6BcxOHsSsdU5yNDFDzR7nJsNbnIniPFg8Q9Qr6WWknEvrCGdjMWWf9_7lC6HxK6pr0w28txFOwbPsX_sKFXQBgzTzBUhKuVYmaMv9koFf4UhODyncmDtfiEicBKtOEGf3Rqz534hTXmHXPb9wLIz4_SfmZAa_nuV8my7oBYrrY95rRE-ZfsV5DCoSbu3AiIK4_n21EYfKVE_6J6S46X8r5WeQvLOQ1A4CEZI2lIAToWdT2T1fVYxrsMpNpcfcHtGF6HaD1h_0rmy_QOgZ8nClDI2tx-DnTWLjMoqNCzWFY1p1nLPx_60xqACFqVl_3vdQ'
}

Call web API with access_token in authorization header​

You will have all right to access resources with access_token. The resources link is like https://login.microsoftonline.com/d5e685a9-3e23-42d9-9efa-15cc5c1ce7db/openid/userinfo. So with access_token in authorization header to call resources link, then RocketServer get the identity info.

	async getIdentity(accessToken) {
const params = {};
const headers = {
'User-Agent': this.userAgent, // http://doc.gitlab.com/ce/api/users.html#Current-user
'Accept': 'application/json',
};

if (this.identityTokenSentVia === 'header') {
headers.Authorization = `Bearer ${accessToken}`;
} else {
params[this.accessTokenParam] = accessToken;
}

console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server getIdentity path 12", this.identityPath);
console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server getIdentity headers 12", headers);
console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server getIdentity params 12", params);

try {
const request = await fetch(`${this.identityPath}`, { method: 'GET', headers, params });
console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server getIdentity request 12", request);

if (!request.ok) {
throw new Error(request.statusText);
}

const response = await request.json();

console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server getIdentity 04", response);

return this.normalizeIdentity(response);
} catch (err) {
const error = new Error(`Failed to fetch identity from ${this.name} at ${this.identityPath}. ${err.message}`);
throw _.extend(error, { response: err.response });
}
}
access_token in authorization header
custom_oauth_server getIdentity headers 12 {
'User-Agent': 'Meteor/METEOR@2.15',
Accept: 'application/json',
Authorization: 'Bearer PAQABAQIAAAApTwJmzXqdR4BN2miheQMYlbd5ZfbyIQUX1iW0FWGmFGvyjTFuy2zHZ1PoDvLgz0D6hAas62MopvXHFIhX53amdiH8_8t-SAGJsjr--GeBeY4Xa3CBx4-1Wg6KutRkg6nZPb-tOO-JSvdlEwlJ_QqPbv_MKZjoqiDbbK0AwbeiQmQm7uTxfk_EjiXxJGM5nYGoHNeg6rslqYYROsjEw2mFf3kyYYFEbUstPfc6Mc_4t3I8QJTNfC0NELnnNHP1b3khDyXtWEeYgcG4v9bNu3_s8MuUJmF1Cv1oWG8p7uTAmpmt84aQ5lnkO3fcxH4yLOH_CPcYLa8JU4K6EaqW7qq8rlvFCTQk0EzWDfhDjfSXESY8zylhFyOqRKsuBVlUiQ7FU42ukPbNDtdJEcwgOm1m9KIFEU5EDUB7ljyPwF_nwgXHnhF6tsU0XEFR3hCAsp0_TgGvtE5xR7mNHC59ZrY86QpcgTHC3lXyDj58VGgZTiAA'
}

Return identity data to RocketChat server​

Identity data
custom_oauth_server getIdentity 04 {
amr: '["pwd","mfa"]',
family_name: 'Gump',
given_name: 'Forrest',
ipaddr: '223.104.42.87',
name: 'Forrest Gump',
oid: '90f7596b-88b6-4768-8204-8c476a73fe25',
rh: '0.AbcAqYXm1SM-2UKe-hXMXBzn2xNWhOMxA8BJnxH7amNCQtL8APU.',
sub: 'KGceLH-HoENIN5H4jVwwaLI2rtpH-S2CTnJCHA0y0ak',
tid: 'd5e685a9-3e23-42d9-9efa-15cc5c1ce7db',
unique_name: 'ForrestGump@Gitcoins.onmicrosoft.com',
upn: 'ForrestGump@Gitcoins.onmicrosoft.com',
uti: 'H1zdM7Pmbk2z9qeOOQUsAQ',
ver: '1.0'
}

Collection Identity Info​

At the end of registerService function, all the info of identity will be collected by RocketServer. The RocketServer will find one identity by key field like email or username, then binding the identity credentialToken and credentialSecret for oauth login in next step.

Rocket.Chat/apps/meteor/app/custom-oauth/server/custom_oauth_server.js
	registerService() {
const self = this;
OAuth.registerService(this.name, 2, null, async (query) => {
const response = await self.getAccessToken(query);
const identity = await self.getIdentity(response.access_token, query);

const serviceData = {
_OAuthCustom: true,
serverURL: self.serverURL,
accessToken: response.access_token,
idToken: response.id_token,
expiresAt: +new Date() + 1000 * parseInt(response.expires_in, 10),
};

if (response.refresh_token) {
serviceData.refreshToken = response.refresh_token;
}

_.extend(serviceData, identity);

const data = {
serviceData,
options: {
profile: {
name: identity.name,
},
},
};

return data;
});
}
Collection Identity Info
custom_oauth_server registerService data 05 {
serviceData: {
_OAuthCustom: true,
serverURL: 'https://login.microsoftonline.com/d5e685a9-3e23-42d9-9efa-15cc5c1ce7db',
accessToken: 'PAQABAQIAAAApTwJmzXqdR4BN2miheQMYlbd5ZfbyIQUX1iW0FWGmFGvyjTFuy2zHZ1PoDvLgz0D6hAas62MopvXHFIhX53amdiH8_8t-SAGJsjr--GeBeY4Xa3CBx4-1Wg6KutRkg6nZPb-tOO-JSvdlEwlJ_QqPbv_MKZjoqiDbbK0AwbeiQmQm7uTxfk_EjiXxJGM5nYGoHNeg6rslqYYROsjEw2mFf3kyYYFEbUstPfc6Mc_4t3I8QJTNfC0NELnnNHP1b3khDyXtWEeYgcG4v9bNu3_s8MuUJmF1Cv1oWG8p7uTAmpmt84aQ5lnkO3fcxH4yLOH_CPcYLa8JU4K6EaqW7qq8rlvFCTQk0EzWDfhDjfSXESY8zylhFyOqRKsuBVlUiQ7FU42ukPbNDtdJEcwgOm1m9KIFEU5EDUB7ljyPwF_nwgXHnhF6tsU0XEFR3hCAsp0_TgGvtE5xR7mNHC59ZrY86QpcgTHC3lXyDj58VGgZTiAA',
idToken: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1HTHFqOThWTkxvWGFGZnBKQ0JwZ0I0SmFLcyIsImtpZCI6Ik1HTHFqOThWTkxvWGFGZnBKQ0JwZ0I0SmFLcyJ9.eyJhdWQiOiJlMzg0NTYxMy0wMzMxLTQ5YzAtOWYxMS1mYjZhNjM0MjQyZDIiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9kNWU2ODVhOS0zZTIzLTQyZDktOWVmYS0xNWNjNWMxY2U3ZGIvIiwiaWF0IjoxNzE5NzEwOTM0LCJuYmYiOjE3MTk3MTA5MzQsImV4cCI6MTcxOTcxNDgzNCwiYW1yIjpbInB3ZCIsIm1mYSJdLCJmYW1pbHlfbmFtZSI6Ikd1bXAiLCJnaXZlbl9uYW1lIjoiRm9ycmVzdCIsImlwYWRkciI6IjIyMy4xMDQuNDIuODciLCJuYW1lIjoiRm9ycmVzdCBHdW1wIiwib2lkIjoiOTBmNzU5NmItODhiNi00NzY4LTgyMDQtOGM0NzZhNzNmZTI1IiwicmgiOiIwLkFiY0FxWVhtMVNNLTJVS2UtaFhNWEJ6bjJ4TldoT014QThCSm54SDdhbU5DUXRMOEFQVS4iLCJzdWIiOiJLR2NlTEgtSG9FTklONUg0alZ3d2FMSTJydHBILVMyQ1RuSkNIQTB5MGFrIiwidGlkIjoiZDVlNjg1YTktM2UyMy00MmQ5LTllZmEtMTVjYzVjMWNlN2RiIiwidW5pcXVlX25hbWUiOiJGb3JyZXN0R3VtcEBHaXRjb2lucy5vbm1pY3Jvc29mdC5jb20iLCJ1cG4iOiJGb3JyZXN0R3VtcEBHaXRjb2lucy5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiJGSkI0a0xDRnlVV21ud1ZvdE93OEFRIiwidmVyIjoiMS4wIn0.oGP8qczVpNjzG03en1LmF6BcxOHsSsdU5yNDFDzR7nJsNbnIniPFg8Q9Qr6WWknEvrCGdjMWWf9_7lC6HxK6pr0w28txFOwbPsX_sKFXQBgzTzBUhKuVYmaMv9koFf4UhODyncmDtfiEicBKtOEGf3Rqz534hTXmHXPb9wLIz4_SfmZAa_nuV8my7oBYrrY95rRE-ZfsV5DCoSbu3AiIK4_n21EYfKVE_6J6S46X8r5WeQvLOQ1A4CEZI2lIAToWdT2T1fVYxrsMpNpcfcHtGF6HaD1h_0rmy_QOgZ8nClDI2tx-DnTWLjMoqNCzWFY1p1nLPx_60xqACFqVl_3vdQ',
expiresAt: 1719714834151,
refreshToken: '0.AbcAqYXm1SM-2UKe-hXMXBzn2xNWhOMxA8BJnxH7amNCQtL8APU.AgABAwEAAAApTwJmzXqdR4BN2miheQMYAgDs_wUA9P_RBLQedJRbR8Ll3VPCsjY6zuGnnG6AaZjrFXJGr7hxvjrMD8Azvdxc2BPjThB6qx9JVac8azajaKio_azzrov5EgHgPxCoX5tIL4mV-Yg98cVi5Xm8kOrCjUupc6l0qXRec5Eq69VHJwIUyJrsxpPc8LsHYwDNoKhPWm7fBVKUW0g5ifxg_RBAF43MJYrC5z65tgBRmXwsRJ7ZgDBqnXelYWLKgd-3HG5MkuRpv3JB8vxGRSS5kKdEExU0VRXJ08MMVNHIX_bdzFKyf8LYB2V1sU7gxnWnTmDgQAwvS0Asp0XE-nPY9JtUs_NkgsWD_JQEXv40ib3kOXWNf07ZnVD0DC4E-qE2G76CoEq0l0OWjH9EHf_xhLr21skTWRnaRnnQ8UN1dpyNrf5ymSkhxvTXNOCA6xZUV_ZhjZmjPixig0-8Qx8xDV4RTqnM2NiV8KiR9sL156j31PIeGEp5cagk_lbBqxU2XwGJ9hKI5JbkN3Jg_YVltVKpfkdv4hYzlD94hkepWngLi7q5Sn-Zv-LAC4F8WguDTPt6GjTtOCTG3Yl6BuiMDiBy4-xYM_5BhTdJ1ZCF8wAm-o86U1F1bWTGMa7KQksPsb6AGFGpRcAhbxfki6lETARM18DMVaogACUASffA0ppkSAKoRofJ9-EvXWqodItnTtFmW9WurbNjd0otb1BRP_EIJqKDjd4IAgwbNT0h2C4rWUMEji6ET1KgHun7AGAUTF2EnawYpEzYomCR-pM',
amr: '["pwd","mfa"]',
family_name: 'Gump',
given_name: 'Forrest',
ipaddr: '223.104.42.87',
name: 'Gump',
oid: '90f7596b-88b6-4768-8204-8c476a73fe25',
rh: '0.AbcAqYXm1SM-2UKe-hXMXBzn2xNWhOMxA8BJnxH7amNCQtL8APU.',
sub: 'KGceLH-HoENIN5H4jVwwaLI2rtpH-S2CTnJCHA0y0ak',
tid: 'd5e685a9-3e23-42d9-9efa-15cc5c1ce7db',
unique_name: 'ForrestGump@Gitcoins.onmicrosoft.com',
upn: 'ForrestGump@Gitcoins.onmicrosoft.com',
uti: 'H1zdM7Pmbk2z9qeOOQUsAQ',
ver: '1.0',
id: 'KGceLH-HoENIN5H4jVwwaLI2rtpH-S2CTnJCHA0y0ak',
username: 'Forrest',
email: 'ForrestGump@Gitcoins.onmicrosoft.com'
}

Login using authentication providers with credentialToken and credentialSecret​

After registerService function execution, all the process of authorization finished and the info of identity collected. So which way for one user to login and which authentication provider will be choose? Firstly RocketClient try to login using authentication providers, the following is login call with credentialToken and credentialSecret. The credentialToken and credentialSecret is obtained in the Return credentialToken and credentialSecret step in RocketChatClient.

Login with credentialToken and credentialSecret
{
"msg": "method",
"method": "login",
"id":"42",
"params": [
{
"oauth": {
"credentialToken":"credential-token",
"credentialSecret":"credential-secret"
}
}
]
}

Oauth providers loop​

When Login function executing, updateOrCreateUserFromExternalServiceAsync in custom_oauth_server.js will try to find which Oauth provider to service login. To find Oauth provider with serviceName by looping the provider list in the RocketChatServer, then using addHookToProcessUser funciton to find one dedicated user by key field which have been configured in the RocketChatServer. So the user now have new addtional properties like email2fa and oauth type for login.

/Rocket.Chat/apps/meteor/app/custom-oauth/server/custom_oauth_server.js
const { updateOrCreateUserFromExternalService } = Accounts;
const updateOrCreateUserFromExternalServiceAsync = async function (...args /* serviceName, serviceData, options*/) {
for await (const hook of BeforeUpdateOrCreateUserFromExternalService) {
console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server hook hook hook hook hook XXXXXXXXXXXXXXXXXXXXXXXXXXx");
await hook.apply(this, args);
}

const [serviceName, serviceData] = args;

const user = updateOrCreateUserFromExternalService.apply(this, args);
const fullUser = await Users.findOneById(user.userId);
if (settings.get('LDAP_Update_Data_On_OAuth_Login')) {
await LDAP.loginAuthenticatedUserRequest(fullUser.username);
}

await callbacks.run('afterValidateNewOAuthUser', {
identity: serviceData,
serviceName,
user: fullUser,
});

return user;
};
/Rocket.Chat/apps/meteor/app/custom-oauth/server/custom_oauth_server.js
addHookToProcessUser() {
BeforeUpdateOrCreateUserFromExternalService.push(async (serviceName, serviceData /* , options*/) => {

console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server addHookToProcessUser serviceName 12", serviceName);
console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server addHookToProcessUser this.name 12", this.name);
console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server addHookToProcessUser this.keyField 12", this.keyField);
console.log("XXXXXXXXXXXXXXXXXXXx custom_oauth_server addHookToProcessUser serviceData 12", serviceData);

if (serviceName !== this.name) {
return;
}

if (serviceData.username) {
let user = undefined;

if (this.keyField === 'username') {
user = this.mergeUsersDistinctServices
? await Users.findOneByUsernameIgnoringCase(serviceData.username)
: await Users.findOneByUsernameAndServiceNameIgnoringCase(serviceData.username, serviceData.id, serviceName);
} else if (this.keyField === 'email') {
user = this.mergeUsersDistinctServices
? await Users.findOneByEmailAddress(serviceData.email)
: await Users.findOneByEmailAddressAndServiceNameIgnoringCase(serviceData.email, serviceData.id, serviceName);
}

if (!user) {
console.log("XXXXXXXXXXXXXXXXXXXXXXXXXXXXx gitcoins return 1");
return;
}

await callbacks.run('afterProcessOAuthUser', { serviceName, serviceData, user });
console.log("XXXXXXXXXXXXXXXXXXXXXXXXXXXXx gitcoins user 18", user);

// User already created or merged and has identical name as before
if (
user.services &&
user.services[serviceName] &&
user.services[serviceName].id === serviceData.id &&
user.name === serviceData.name &&
(this.keyField === 'email' || !serviceData.email || user.emails?.find(({ address }) => address === serviceData.email))
) {
console.log("XXXXXXXXXXXXXXXXXXXXXXXXXXXXx gitcoins return 2");
return;
}

console.log("XXXXXXXXXXXXXXXXXXXXXXXXXXXXx gitcoins user 19", user);
if (this.mergeUsers !== true) {
throw new Meteor.Error('CustomOAuth', `User with username ${user.username} already exists`);
}

const serviceIdKey = `services.${serviceName}.id`;
const update = {
$set: {
name: serviceData.name,
...(this.keyField === 'username' && serviceData.email && { emails: [{ address: serviceData.email, verified: true }] }),
[serviceIdKey]: serviceData.id,
},
};

console.log("XXXXXXXXXXXXXXXXXXXXXXXXXXXXx gitcoins user 20", user);
await Users.update({ _id: user._id }, update);
}
});
Output Log: How to find Oauth provider by serviceName
XXXXXXXXXXXXXXXXXXXx custom_oauth_server hook hook hook hook hook XXXXXXXXXXXXXXXXXXXXXXXXXXx
XXXXXXXXXXXXXXXXXXXx custom_oauth_server addHookToProcessUser serviceName 12 365oauth
XXXXXXXXXXXXXXXXXXXx custom_oauth_server addHookToProcessUser this.name 12 gitlab
XXXXXXXXXXXXXXXXXXXx custom_oauth_server addHookToProcessUser this.keyField 12 undefined
XXXXXXXXXXXXXXXXXXXx custom_oauth_server addHookToProcessUser serviceData 12 {
_OAuthCustom: true,
serverURL: 'https://login.microsoftonline.com/d5e685a9-3e23-42d9-9efa-15cc5c1ce7db',
accessToken: 'PAQABAQIAAAApTwJmzXqdR4BN2miheQMYlbd5ZfbyIQUX1iW0FWGmFGvyjTFuy2zHZ1PoDvLgz0D6hAas62MopvXHFIhX53amdiH8_8t-SAGJsjr--GeBeY4Xa3CBx4-1Wg6KutRkg6nZPb-tOO-JSvdlEwlJ_QqPbv_MKZjoqiDbbK0AwbeiQmQm7uTxfk_EjiXxJGM5nYGoHNeg6rslqYYROsjEw2mFf3kyYYFEbUstPfc6Mc_4t3I8QJTNfC0NELnnNHP1b3khDyXtWEeYgcG4v9bNu3_s8MuUJmF1Cv1oWG8p7uTAmpmt84aQ5lnkO3fcxH4yLOH_CPcYLa8JU4K6EaqW7qq8rlvFCTQk0EzWDfhDjfSXESY8zylhFyOqRKsuBVlUiQ7FU42ukPbNDtdJEcwgOm1m9KIFEU5EDUB7ljyPwF_nwgXHnhF6tsU0XEFR3hCAsp0_TgGvtE5xR7mNHC59ZrY86QpcgTHC3lXyDj58VGgZTiAA',
idToken: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1HTHFqOThWTkxvWGFGZnBKQ0JwZ0I0SmFLcyIsImtpZCI6Ik1HTHFqOThWTkxvWGFGZnBKQ0JwZ0I0SmFLcyJ9.eyJhdWQiOiJlMzg0NTYxMy0wMzMxLTQ5YzAtOWYxMS1mYjZhNjM0MjQyZDIiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9kNWU2ODVhOS0zZTIzLTQyZDktOWVmYS0xNWNjNWMxY2U3ZGIvIiwiaWF0IjoxNzE5NzEwOTM0LCJuYmYiOjE3MTk3MTA5MzQsImV4cCI6MTcxOTcxNDgzNCwiYW1yIjpbInB3ZCIsIm1mYSJdLCJmYW1pbHlfbmFtZSI6Ikd1bXAiLCJnaXZlbl9uYW1lIjoiRm9ycmVzdCIsImlwYWRkciI6IjIyMy4xMDQuNDIuODciLCJuYW1lIjoiRm9ycmVzdCBHdW1wIiwib2lkIjoiOTBmNzU5NmItODhiNi00NzY4LTgyMDQtOGM0NzZhNzNmZTI1IiwicmgiOiIwLkFiY0FxWVhtMVNNLTJVS2UtaFhNWEJ6bjJ4TldoT014QThCSm54SDdhbU5DUXRMOEFQVS4iLCJzdWIiOiJLR2NlTEgtSG9FTklONUg0alZ3d2FMSTJydHBILVMyQ1RuSkNIQTB5MGFrIiwidGlkIjoiZDVlNjg1YTktM2UyMy00MmQ5LTllZmEtMTVjYzVjMWNlN2RiIiwidW5pcXVlX25hbWUiOiJGb3JyZXN0R3VtcEBHaXRjb2lucy5vbm1pY3Jvc29mdC5jb20iLCJ1cG4iOiJGb3JyZXN0R3VtcEBHaXRjb2lucy5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiJGSkI0a0xDRnlVV21ud1ZvdE93OEFRIiwidmVyIjoiMS4wIn0.oGP8qczVpNjzG03en1LmF6BcxOHsSsdU5yNDFDzR7nJsNbnIniPFg8Q9Qr6WWknEvrCGdjMWWf9_7lC6HxK6pr0w28txFOwbPsX_sKFXQBgzTzBUhKuVYmaMv9koFf4UhODyncmDtfiEicBKtOEGf3Rqz534hTXmHXPb9wLIz4_SfmZAa_nuV8my7oBYrrY95rRE-ZfsV5DCoSbu3AiIK4_n21EYfKVE_6J6S46X8r5WeQvLOQ1A4CEZI2lIAToWdT2T1fVYxrsMpNpcfcHtGF6HaD1h_0rmy_QOgZ8nClDI2tx-DnTWLjMoqNCzWFY1p1nLPx_60xqACFqVl_3vdQ',
expiresAt: 1719714834151,
refreshToken: '0.AbcAqYXm1SM-2UKe-hXMXBzn2xNWhOMxA8BJnxH7amNCQtL8APU.AgABAwEAAAApTwJmzXqdR4BN2miheQMYAgDs_wUA9P_RBLQedJRbR8Ll3VPCsjY6zuGnnG6AaZjrFXJGr7hxvjrMD8Azvdxc2BPjThB6qx9JVac8azajaKio_azzrov5EgHgPxCoX5tIL4mV-Yg98cVi5Xm8kOrCjUupc6l0qXRec5Eq69VHJwIUyJrsxpPc8LsHYwDNoKhPWm7fBVKUW0g5ifxg_RBAF43MJYrC5z65tgBRmXwsRJ7ZgDBqnXelYWLKgd-3HG5MkuRpv3JB8vxGRSS5kKdEExU0VRXJ08MMVNHIX_bdzFKyf8LYB2V1sU7gxnWnTmDgQAwvS0Asp0XE-nPY9JtUs_NkgsWD_JQEXv40ib3kOXWNf07ZnVD0DC4E-qE2G76CoEq0l0OWjH9EHf_xhLr21skTWRnaRnnQ8UN1dpyNrf5ymSkhxvTXNOCA6xZUV_ZhjZmjPixig0-8Qx8xDV4RTqnM2NiV8KiR9sL156j31PIeGEp5cagk_lbBqxU2XwGJ9hKI5JbkN3Jg_YVltVKpfkdv4hYzlD94hkepWngLi7q5Sn-Zv-LAC4F8WguDTPt6GjTtOCTG3Yl6BuiMDiBy4-xYM_5BhTdJ1ZCF8wAm-o86U1F1bWTGMa7KQksPsb6AGFGpRcAhbxfki6lETARM18DMVaogACUASffA0ppkSAKoRofJ9-EvXWqodItnTtFmW9WurbNjd0otb1BRP_EIJqKDjd4IAgwbNT0h2C4rWUMEji6ET1KgHun7AGAUTF2EnawYpEzYomCR-pM',
amr: '["pwd","mfa"]',
family_name: 'Gump',
given_name: 'Forrest',
ipaddr: '223.104.42.87',
name: 'Gump',
oid: '90f7596b-88b6-4768-8204-8c476a73fe25',
rh: '0.AbcAqYXm1SM-2UKe-hXMXBzn2xNWhOMxA8BJnxH7amNCQtL8APU.',
sub: 'KGceLH-HoENIN5H4jVwwaLI2rtpH-S2CTnJCHA0y0ak',
tid: 'd5e685a9-3e23-42d9-9efa-15cc5c1ce7db',
unique_name: 'ForrestGump@Gitcoins.onmicrosoft.com',
upn: 'ForrestGump@Gitcoins.onmicrosoft.com',
uti: 'H1zdM7Pmbk2z9qeOOQUsAQ',
ver: '1.0',
id: 'KGceLH-HoENIN5H4jVwwaLI2rtpH-S2CTnJCHA0y0ak',
username: 'Forrest',
email: 'ForrestGump@Gitcoins.onmicrosoft.com'
}



XXXXXXXXXXXXXXXXXXXx custom_oauth_server hook hook hook hook hook XXXXXXXXXXXXXXXXXXXXXXXXXXx
XXXXXXXXXXXXXXXXXXXx custom_oauth_server addHookToProcessUser serviceName 12 365oauth
XXXXXXXXXXXXXXXXXXXx custom_oauth_server addHookToProcessUser this.name 12 365oauth
XXXXXXXXXXXXXXXXXXXx custom_oauth_server addHookToProcessUser this.keyField 12 username
XXXXXXXXXXXXXXXXXXXx custom_oauth_server addHookToProcessUser serviceData 12 {
_OAuthCustom: true,
serverURL: 'https://login.microsoftonline.com/d5e685a9-3e23-42d9-9efa-15cc5c1ce7db',
accessToken: 'PAQABAQIAAAApTwJmzXqdR4BN2miheQMYlbd5ZfbyIQUX1iW0FWGmFGvyjTFuy2zHZ1PoDvLgz0D6hAas62MopvXHFIhX53amdiH8_8t-SAGJsjr--GeBeY4Xa3CBx4-1Wg6KutRkg6nZPb-tOO-JSvdlEwlJ_QqPbv_MKZjoqiDbbK0AwbeiQmQm7uTxfk_EjiXxJGM5nYGoHNeg6rslqYYROsjEw2mFf3kyYYFEbUstPfc6Mc_4t3I8QJTNfC0NELnnNHP1b3khDyXtWEeYgcG4v9bNu3_s8MuUJmF1Cv1oWG8p7uTAmpmt84aQ5lnkO3fcxH4yLOH_CPcYLa8JU4K6EaqW7qq8rlvFCTQk0EzWDfhDjfSXESY8zylhFyOqRKsuBVlUiQ7FU42ukPbNDtdJEcwgOm1m9KIFEU5EDUB7ljyPwF_nwgXHnhF6tsU0XEFR3hCAsp0_TgGvtE5xR7mNHC59ZrY86QpcgTHC3lXyDj58VGgZTiAA',
idToken: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1HTHFqOThWTkxvWGFGZnBKQ0JwZ0I0SmFLcyIsImtpZCI6Ik1HTHFqOThWTkxvWGFGZnBKQ0JwZ0I0SmFLcyJ9.eyJhdWQiOiJlMzg0NTYxMy0wMzMxLTQ5YzAtOWYxMS1mYjZhNjM0MjQyZDIiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9kNWU2ODVhOS0zZTIzLTQyZDktOWVmYS0xNWNjNWMxY2U3ZGIvIiwiaWF0IjoxNzE5NzEwOTM0LCJuYmYiOjE3MTk3MTA5MzQsImV4cCI6MTcxOTcxNDgzNCwiYW1yIjpbInB3ZCIsIm1mYSJdLCJmYW1pbHlfbmFtZSI6Ikd1bXAiLCJnaXZlbl9uYW1lIjoiRm9ycmVzdCIsImlwYWRkciI6IjIyMy4xMDQuNDIuODciLCJuYW1lIjoiRm9ycmVzdCBHdW1wIiwib2lkIjoiOTBmNzU5NmItODhiNi00NzY4LTgyMDQtOGM0NzZhNzNmZTI1IiwicmgiOiIwLkFiY0FxWVhtMVNNLTJVS2UtaFhNWEJ6bjJ4TldoT014QThCSm54SDdhbU5DUXRMOEFQVS4iLCJzdWIiOiJLR2NlTEgtSG9FTklONUg0alZ3d2FMSTJydHBILVMyQ1RuSkNIQTB5MGFrIiwidGlkIjoiZDVlNjg1YTktM2UyMy00MmQ5LTllZmEtMTVjYzVjMWNlN2RiIiwidW5pcXVlX25hbWUiOiJGb3JyZXN0R3VtcEBHaXRjb2lucy5vbm1pY3Jvc29mdC5jb20iLCJ1cG4iOiJGb3JyZXN0R3VtcEBHaXRjb2lucy5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiJGSkI0a0xDRnlVV21ud1ZvdE93OEFRIiwidmVyIjoiMS4wIn0.oGP8qczVpNjzG03en1LmF6BcxOHsSsdU5yNDFDzR7nJsNbnIniPFg8Q9Qr6WWknEvrCGdjMWWf9_7lC6HxK6pr0w28txFOwbPsX_sKFXQBgzTzBUhKuVYmaMv9koFf4UhODyncmDtfiEicBKtOEGf3Rqz534hTXmHXPb9wLIz4_SfmZAa_nuV8my7oBYrrY95rRE-ZfsV5DCoSbu3AiIK4_n21EYfKVE_6J6S46X8r5WeQvLOQ1A4CEZI2lIAToWdT2T1fVYxrsMpNpcfcHtGF6HaD1h_0rmy_QOgZ8nClDI2tx-DnTWLjMoqNCzWFY1p1nLPx_60xqACFqVl_3vdQ',
expiresAt: 1719714834151,
refreshToken: '0.AbcAqYXm1SM-2UKe-hXMXBzn2xNWhOMxA8BJnxH7amNCQtL8APU.AgABAwEAAAApTwJmzXqdR4BN2miheQMYAgDs_wUA9P_RBLQedJRbR8Ll3VPCsjY6zuGnnG6AaZjrFXJGr7hxvjrMD8Azvdxc2BPjThB6qx9JVac8azajaKio_azzrov5EgHgPxCoX5tIL4mV-Yg98cVi5Xm8kOrCjUupc6l0qXRec5Eq69VHJwIUyJrsxpPc8LsHYwDNoKhPWm7fBVKUW0g5ifxg_RBAF43MJYrC5z65tgBRmXwsRJ7ZgDBqnXelYWLKgd-3HG5MkuRpv3JB8vxGRSS5kKdEExU0VRXJ08MMVNHIX_bdzFKyf8LYB2V1sU7gxnWnTmDgQAwvS0Asp0XE-nPY9JtUs_NkgsWD_JQEXv40ib3kOXWNf07ZnVD0DC4E-qE2G76CoEq0l0OWjH9EHf_xhLr21skTWRnaRnnQ8UN1dpyNrf5ymSkhxvTXNOCA6xZUV_ZhjZmjPixig0-8Qx8xDV4RTqnM2NiV8KiR9sL156j31PIeGEp5cagk_lbBqxU2XwGJ9hKI5JbkN3Jg_YVltVKpfkdv4hYzlD94hkepWngLi7q5Sn-Zv-LAC4F8WguDTPt6GjTtOCTG3Yl6BuiMDiBy4-xYM_5BhTdJ1ZCF8wAm-o86U1F1bWTGMa7KQksPsb6AGFGpRcAhbxfki6lETARM18DMVaogACUASffA0ppkSAKoRofJ9-EvXWqodItnTtFmW9WurbNjd0otb1BRP_EIJqKDjd4IAgwbNT0h2C4rWUMEji6ET1KgHun7AGAUTF2EnawYpEzYomCR-pM',
amr: '["pwd","mfa"]',
family_name: 'Gump',
given_name: 'Forrest',
ipaddr: '223.104.42.87',
name: 'Gump',
oid: '90f7596b-88b6-4768-8204-8c476a73fe25',
rh: '0.AbcAqYXm1SM-2UKe-hXMXBzn2xNWhOMxA8BJnxH7amNCQtL8APU.',
sub: 'KGceLH-HoENIN5H4jVwwaLI2rtpH-S2CTnJCHA0y0ak',
tid: 'd5e685a9-3e23-42d9-9efa-15cc5c1ce7db',
unique_name: 'ForrestGump@Gitcoins.onmicrosoft.com',
upn: 'ForrestGump@Gitcoins.onmicrosoft.com',
uti: 'H1zdM7Pmbk2z9qeOOQUsAQ',
ver: '1.0',
id: 'KGceLH-HoENIN5H4jVwwaLI2rtpH-S2CTnJCHA0y0ak',
username: 'Forrest',
email: 'ForrestGump@Gitcoins.onmicrosoft.com'
}
User properties with Oauth type
XXXXXXXXXXXXXXXXXXXXXXXXXXXXx gitcoins return 2
..................... authenticaiton server Account validateLoginAttemptAsync 10 {
type: '365oauth',
allowed: true,
methodName: 'login',
methodArguments: [ { oauth: [Object] } ],
user: {
_id: 'HMDuxBovxYtNuxtuE',
createdAt: 2024-06-05T04:06:25.944Z,
services: {
email2fa: [Object],
email: [Object],
resume: [Object],
'365oauth': [Object],
password: [Object],
microsoft356: [Object],
emailCode: [Object]
},
emails: [ [Object] ],
type: 'user',
roles: [ 'user' ],
status: 'offline',
active: true,
_updatedAt: 2024-06-25T01:51:50.840Z,
name: 'Gump',
username: 'forrest',
__rooms: [ 'GENERAL' ],
lastLogin: 2024-06-25T01:51:13.025Z,
statusConnection: 'offline',
utcOffset: 8,
statusText: '',
statusDefault: 'online',
settings: { profile: {} }
},
connection: {
id: 'hDZQhgrS925j22fZN',
close: [Function: close],
httpHeaders: {
'x-forwarded-port': '80',
'x-forwarded-proto': 'https,http',
'x-forwarded-host': '212c-34-92-204-228.ngrok-free.app',
'x-forwarded-for': '2a11:3:200::2057,127.0.0.1',
'sec-fetch-site': 'same-origin',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
referer: 'https://212c-34-92-204-228.ngrok-free.app/home',
priority: 'u=1, i',
origin: 'https://212c-34-92-204-228.ngrok-free.app',
'content-type': 'application/json',
'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7',
'accept-encoding': 'gzip, deflate, br, zstd',
accept: 'application/json',
'content-length': '232',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
host: '212c-34-92-204-228.ngrok-free.app',
connection: 'keep-alive'
},
clientAddress: '127.0.0.1',
token: undefined
}
}

2FA login​

2FA (Two-Factor Authentication) in the RocketChat context, after verify the identiy info from Identity Authorization Platform, the TOTP (Time-Based One-Time Password) will be send out to your email. After input TOTP to login page, then login successfuly. When try to 2FA with TOTP, there is exeception happend to trigger the RocketChatServer to send the TOTP to your email.

Login Exception to trigger TOTP send out
Exception while invoking method login errorClass [Error]: TOTP Required [totp-required]
at app/2fa/server/code/index.ts:211:9
at /home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
=> awaited here:
at Function.Promise.await (/home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
at app/2fa/server/loginHandler.ts:44:3
at /home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
=> awaited here:
at Function.Promise.await (/home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
at lib/callbacks/callbacksBase.ts:68:5
at /home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
=> awaited here:
at Function.Promise.await (/home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
at app/authentication/server/startup/index.js:434:7
at /home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
=> awaited here:
at Function.Promise.await (/home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
at app/authentication/server/startup/index.js:455:17
at packages/callback-hook/hook.js:177:18
at packages/meteor.js:365:18
at packages/accounts-base/accounts_server.js:226:15
at Hook.forEach (packages/callback-hook/hook.js:124:15)
at AccountsServer._validateLogin (packages/accounts-base/accounts_server.js:223:29)
at packages/accounts-base/accounts_server.js:473:10
at /home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
=> awaited here:
at Function.Promise.await (/home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
at packages/accounts-base/accounts_server.js:653:7
at /home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
=> awaited here:
at Function.Promise.await (/home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
at packages/meteor.js:367:22
at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1389:31)
at packages/ddp-server/livedata_server.js:1854:36
at new Promise (<anonymous>)
at Server.applyAsync (packages/ddp-server/livedata_server.js:1853:12)
at Server.callAsync (packages/ddp-server/livedata_server.js:1779:17)
at app/api/server/v1/misc.ts:620:33
at /home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
=> awaited here:
at Function.Promise.await (/home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_server.js:56:12)
at app/api/server/v1/misc.ts:620:17
at /home/gitcoins/.meteor/packages/promise/.0.12.2.1v51k7w.6dob++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40 {
isClientSafe: true,
error: 'totp-required',
reason: 'TOTP Required',
details: {
method: 'email',
codeGenerated: true,
emailOrUsername: 'forrest',
availableMethods: [ 'email' ]
},
errorType: 'Meteor.Error'
}
====== BEGIN MAIL #34 ======
(Mail not sent; to enable sending, set the MAIL_URL environment variable.)
Content-Type: multipart/alternative;
boundary="--_NmP-7245379ef3eb6267-Part_1"
To: ForrestGump@gitcoins.onmicrosoft.com
Subject: Authentication code
Message-ID: <b062f356-f598-1d0e-d7e4-5728ab80ebaa@rocket-chat-server>
Date: Sun, 30 Jun 2024 01:34:00 +0000
MIME-Version: 1.0

----_NmP-7245379ef3eb6267-Part_1
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit


Here is your authentication code:

274-035
info
  • credentialToken in all steps must be same in one login session.
  • redirect_uri in all steps must be same in one login session.
  • state in all steps must be same in one login session.