Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added silent login functionality, moved testing library as dev dependencies #109

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,22 @@ const RedirectPage = () => {
}, []);
};
```

### Using the Silent Login Function

Use this functionality when you application needs to be automatically redirected and sign in when the user has previously logged in from other applications.

For instance, user signs in from Deriv.app and goes into your application (e.g. SmartTrader). To ensure that when the user lands in SmartTrader and automatically sign-in the user, you will need to call the function `requestSilentLogin` at the root of your app:

```typescript
// YourRootApp/Wrapper.tsx

useEffect(() => {
const clientAccounts = JSON.parse(localStorage.getItem('client.accounts') || '{}')
requestSilentLogin({
clientAccounts,
redirectCallbackUri: `${window.location.origin}/en/callback`, // (this depends on your app, for SmartTrader its /en/callback, but for most apps its /callback)
endpointUri: '/en/endpoint'
})
}, [])
```
37 changes: 32 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
},
"dependencies": {
"@deriv-com/utils": "^0.0.37",
"@testing-library/react": "^16.0.1",
"js-cookie": "3.0.5",
"oidc-client-ts": "^3.1.0"
},
"devDependencies": {
"@testing-library/react": "^16.0.1",
"@babel/preset-env": "^7.26.0",
"@babel/preset-typescript": "^7.26.0",
"@eslint/js": "^9.9.0",
Expand Down
50 changes: 50 additions & 0 deletions src/oidc/oidc.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Cookies from 'js-cookie';
import { UserManager, WebStorageStateStore } from 'oidc-client-ts';
import { OIDCError, OIDCErrorType } from './error';
import { getServerInfo } from '../constants';
Expand Down Expand Up @@ -40,6 +41,12 @@ type CreateUserManagerOptions = {
postLogoutRedirectUri?: string;
};

type RequestSilentLoginOptions = {
clientAccounts: Record<string | number, unknown>;
redirectCallbackUri: string;
endpointUri?: string;
};

/**
* Fetches the OIDC configuration for the given serverUrl.
* @returns {Promise<object>} - A promise resolving to the OIDC configuration.
Expand Down Expand Up @@ -228,6 +235,49 @@ export const requestLegacyToken = async (accessToken: string): Promise<LegacyTok
}
};

/**
* Initiates a silent login process only when:
* - User must have previously logged in (checked via 'logged_state' cookie set to `true`)
* - Current page is not a callback/endpoint page
* - Client accounts data in localStorage does not exist
*
* @async
* @param {Object} options - Configuration options for the silent login request
* @param {string} options.clientAccounts - The client accounts data. This must be retrieved on your side using your app's preferred storage method (localStorage or sessionStorage)
* @param {string} options.redirectCallbackUri - The URI path for redirect callback after authentication
* @param {string} [options.endpointUri] - Optional URI path to exclude from silent login attempts
*
* @example
*
* const clientAccounts = JSON.parse(localStorage.getItem('client.accounts') || '{}')
* await requestSilentLogin({
* clientAccounts: clientAccounts,
* redirectCallbackUri: `${window.location.origin}/callback`,
* endpointUri: '/endpoint'
* });
*
* @throws {Error} May throw errors from requestOidcAuthentication if authentication fails
* @returns {Promise<void>} Returns a Promise that resolves when the authentication process is complete
*/
export const requestSilentLogin = async (options: RequestSilentLoginOptions) => {
const isLoggedInCookie = Cookies.get('logged_state') === 'true';
// The reason why we are asking the user to pass in the entire client.accounts object is because they may store the data in either localStorage or sessionStorage
const isClientAccountsPopulated = Object.keys(options.clientAccounts).length > 0;
const isCallbackPage = window.location.href.startsWith(options.redirectCallbackUri);
const isEndpointPage = options.endpointUri ? window.location.pathname.includes(options.endpointUri) : false;

// we only do SSO if:
// we have previously logged-in before from SmartTrader or any other apps (Deriv.app, etc) - isLoggedInCookie
// if we are not in the callback route to prevent re-calling this function - !isCallbackPage
// if client.accounts in localStorage is empty - !isClientAccountsPopulated
// and if feature flag for OIDC Phase 2 is enabled - isAuthEnabled
if (isLoggedInCookie && !isCallbackPage && !isEndpointPage && !isClientAccountsPopulated) {
await requestOidcAuthentication({
redirectCallbackUri: options.redirectCallbackUri,
});
}
};

/**
* Creates a UserManager instance that will be used to manage and call the OIDC flow
* @param options - Configuration options for the OIDC token request
Expand Down
Loading