Skip to content

Commit

Permalink
feat: support authorization with RingCentral jwt token (#107)
Browse files Browse the repository at this point in the history
* feat: support authorization with jwt token

* chore: add jwt auth doc
  • Loading branch information
embbnux authored Aug 15, 2024
1 parent 5a02058 commit 40945b6
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 10 deletions.
39 changes: 35 additions & 4 deletions docs/customize-authorization.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
# Customize Authorization
# Alternative authorization methods

In widget, it popups a window with [RingCentral authorization uri](https://developers.ringcentral.com/api-reference/Authorization) from [authorization code with PKCE flow](https://medium.com/ringcentral-developers/use-authorization-code-pkce-for-ringcentral-api-in-client-app-e9108f04b5f0) for user login. But you can also implement the popup window out of the widget, and pass authorization code to widget after you get authorization callback.
In the widget, it popups a window with [RingCentral authorization uri](https://developers.ringcentral.com/api-reference/Authorization) from [authorization code with PKCE flow](https://medium.com/ringcentral-developers/use-authorization-code-pkce-for-ringcentral-api-in-client-app-e9108f04b5f0) when user click login button. This is the recommended authorization method for applications like those built on top of RingCX Embeddable. Therefore, no changes are necessary to enable authorization and usage of RingCX Embeddable. However, some developers in specific and rare circumstances may wish to utilize a different method of authorization. This guide will instruct developers on how to do so.

## JWT flow

Developers can login to RingCX Embeddable using the [JWT auth flow](https://developers.ringcentral.com/guide/authentication/jwt/quick-start) if they so choose. A JWT token is bind to a RingCentral user when it is generated. If developer shares the same JWT token to multiple users, it means that every user of RingCX Embeddable will be logged in as the same RingCentral user, which may undermine the value of RingCentral's audit trail and security practices. Please use at your own risk. For different users, developers need to help them to generate different JWT tokens with their own RingCentral credentials.

!!! warning "JWT auth flow in Embeddable is experimental"
While the JWT auth flow itself is not experimental, its usage within the context of RingCX Embeddable is. This is due to the fact that using JWT in this way is beyond the intended design of Embeddable, and could be problematic in some circumstances.

JWT also requires you to expose your client secret, which if exposed publicly could expose you to some security risks.

### Adapter JS way

```js
(function() {
var rcs = document.createElement("script");
rcs.src = "https://ringcentral.github.io/engage-voice-embeddable/adapter.js?clientId=YOU_OWN_JWT_APP_CLIENT_ID&clientSecret=YOUR_OWN_JWT_APP_CLIENT_SECRET&jwt=JWT_TOKEN";
var rcs0 = document.getElementsByTagName("script")[0];
rcs0.parentNode.insertBefore(rcs, rcs0);
})();
```

### Iframe way

```html
<iframe width="300" height="500" id="engage-voice-embeddable-adapter-frame" allow="microphone" src="https://ringcentral.github.io/engage-voice-embeddable/app.html?clientId=YOU_OWN_JWT_APP_CLIENT_ID&clientSecret=YOUR_OWN_JWT_APP_CLIENT_SECRET&jwt=JWT_TOKEN">
</iframe>
```

## Authorization code flow with PKCE

This section is for developers who want to manage login popup window.

**Prerequisites**: [Customize `clientId`](customize-client-id.md)

## Get authorization uri
### Get authorization uri

For authorization code with PKCE, the widget will create `code_verifier` and `code_challenge` pair for generating oauth uri and exchanging token. So we need to get oauth login URI from it.

Expand Down Expand Up @@ -35,7 +66,7 @@ window.addEventListener('message', (e) => {
});
```

## Pass RingCentral authorization code:
### Pass RingCentral authorization code:

```js
document.querySelector("#engage-voice-embeddable-adapter-frame").contentWindow.postMessage({
Expand Down
2 changes: 2 additions & 0 deletions src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const {
disableLoginPopup,
enablePopup,
popupPageUri,
jwt,
} = parseUri(paramsUri);

function obj2uri(obj) {
Expand All @@ -56,6 +57,7 @@ const appUri = `${appUrl}?${obj2uri({
disableLoginPopup,
fromAdapter: 1,
fromPopup,
jwt,
_t: Date.now(),
})}`;

Expand Down
5 changes: 3 additions & 2 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const {
evServer,
disableLoginPopup,
fromPopup,
jwt,
} = pathParams;

if (clientId) {
Expand All @@ -48,15 +49,15 @@ const phone = createPhone({
sdkConfig,
brandConfig,
evSdkConfig,
redirectUri,
authConfig,
redirectUri: redirectUri || authConfig.redirectUri,
prefix,
version,
buildHash,
hideCallNote: !!hideCallNote,
runTimeEnvironment: process.env.NODE_ENV,
disableLoginPopup: !!disableLoginPopup,
fromPopup,
jwt,
});

const store = createStore(phone.reducer);
Expand Down
6 changes: 3 additions & 3 deletions src/brands/rc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export default {
brandCode: 'rc',
code: 'rc',
name: 'RingCentral',
appName: 'RingCentral Engage Cloud Contact Center',
fullName: 'RingCentral Engage Cloud Contact Center',
application: 'RingCentral Engage Cloud Contact Center',
appName: 'RingCX Embeddable',
fullName: 'RingCentral RingCX Embeddable',
application: 'RingCX Embeddable',
};
42 changes: 41 additions & 1 deletion src/modules/OAuth/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Module } from '@ringcentral-integration/commons/lib/di';
import OAuthBase from '@ringcentral-integration/widgets/modules/OAuth';

import { loginStatus } from '@ringcentral-integration/commons/modules/Auth/loginStatus';
import { watch } from '@ringcentral-integration/core';
import messageTypes from '../../enums/messageTypes';

@Module({
Expand All @@ -10,6 +11,8 @@ import messageTypes from '../../enums/messageTypes';
]
})
export default class OAuth extends OAuthBase {
protected _userLogout = false;

openOAuthPage() {
if (this._deps.oAuthOptions.disableLoginPopup) {
window.parent.postMessage({
Expand All @@ -20,5 +23,42 @@ export default class OAuth extends OAuthBase {
}
super.openOAuthPage();
}

override onInitOnce() {
super.onInitOnce();
watch(
this,
() => [
this.ready,
this._deps.auth.loginStatus,
],
async () => {
if (!this.ready) {
return;
}
if (this._deps.auth.loginStatus === loginStatus.beforeLogout) {
// Do not jwt login after logout
this._userLogout = true;
}
if (this._userLogout) {
return;
}
if (
!this._deps.auth.notLoggedIn
) {
return;
}
if (this._deps.oAuthOptions.jwt) {
this._deps.auth.setLogin();
this._deps.client.service.platform().login({
jwt: this._deps.oAuthOptions.jwt,
});
}
},
{
multiple: true,
},
);
}
}

2 changes: 2 additions & 0 deletions src/modules/Phone/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ export function createPhone({
disableLoginPopup,
redirectUri,
fromPopup,
jwt,
}) {
const appVersion = buildHash ? `${version} (${buildHash})` : version;
const usePKCE = sdkConfig.clientId && !sdkConfig.clientSecret;
Expand Down Expand Up @@ -465,6 +466,7 @@ export function createPhone({
extralUIOptions: ['hide_remember_me', 'hide_tos', '-old_ui'],
disableLoginPopup,
redirectUri,
jwt,
},
},
{ provide: 'AuthOptions', useValue: { usePKCE } },
Expand Down

0 comments on commit 40945b6

Please sign in to comment.