diff --git a/docs/media/authentication_guide.md b/docs/media/authentication_guide.md index 3d869e8758e..bdf2ab5ba03 100644 --- a/docs/media/authentication_guide.md +++ b/docs/media/authentication_guide.md @@ -169,6 +169,14 @@ import { Auth } from 'aws-amplify'; Auth.signOut() .then(data => console.log(data)) .catch(err => console.log(err)); + +// By doing this, you are revoking all the auth tokens(id token, access token and refresh token) +// which means the user is signed out from all the devices +// Note: although the tokens are revoked, the AWS credentials will remain valid until they expire (which by default is 1 hour) +Auth.signOut({ global: true }) + .then(data => console.log(data)) + .catch(err => console.log(err)); + ``` #### Change password diff --git a/packages/auth/__tests__/auth-unit-test.ts b/packages/auth/__tests__/auth-unit-test.ts index 36834335f7b..39747c41963 100644 --- a/packages/auth/__tests__/auth-unit-test.ts +++ b/packages/auth/__tests__/auth-unit-test.ts @@ -113,6 +113,10 @@ jest.mock('amazon-cognito-identity-js/lib/CognitoUser', () => { }; + CognitoUser.prototype.globalSignOut = (callback) => { + callback.onSuccess(); + }; + CognitoUser.prototype.confirmRegistration = (confirmationCode, forceAliasCreation, callback) => { callback(null, 'Success'); }; @@ -1349,7 +1353,7 @@ describe('auth unit test', () => { }); }); - describe('signOut', () => { + describe('signOut test', () => { test('happy case', async () => { const auth = new Auth(authOptions); @@ -1408,6 +1412,33 @@ describe('auth unit test', () => { spyon2.mockClear(); }); + test('happy case for globalSignOut', async () => { + const auth = new Auth(authOptions); + const user = new CognitoUser({ + Username: 'username', + Pool: userPool + }); + + const spyonAuth = jest.spyOn(Credentials, "clear") + .mockImplementationOnce(() => { + return Promise.resolve(); + }); + const spyon = jest.spyOn(CognitoUserPool.prototype, "getCurrentUser") + .mockImplementationOnce(() => { + return user; + }); + const spyon2 = jest.spyOn(CognitoUser.prototype, "globalSignOut"); + + await auth.signOut({global: true}); + + expect.assertions(1); + expect(spyon2).toBeCalled(); + + spyonAuth.mockClear(); + spyon.mockClear(); + spyon2.mockClear(); + }); + test('happy case for no userpool', async () => { // @ts-ignore const auth = new Auth(authOptionsWithNoUserPoolId); diff --git a/packages/auth/src/Auth.ts b/packages/auth/src/Auth.ts index 730bcde58f5..e895395be14 100644 --- a/packages/auth/src/Auth.ts +++ b/packages/auth/src/Auth.ts @@ -11,7 +11,7 @@ * and limitations under the License. */ -import { AuthOptions, FederatedResponse, ConfirmSignUpOptions } from './types'; +import { AuthOptions, FederatedResponse, ConfirmSignUpOptions, SignOutOpts } from './types'; import { AWS, @@ -1083,11 +1083,35 @@ export default class AuthClass { return that.currentUserPoolUser() .then(user => that.verifyUserAttributeSubmit(user, attr, code)); } + + private async cognitoIdentitySignOut(opts: SignOutOpts, user) { + return new Promise((res, rej) => { + if (opts && opts.global) { + logger.debug('user global sign out', user); + user.globalSignOut({ + onSuccess: (data) => { + logger.debug('global sign out success'); + return res(); + }, + onFailure: (err) => { + logger.debug('global sign out failed', err); + return rej(err); + } + }); + } else { + logger.debug('user sign out', user); + user.signOut(); + return res(); + } + }); + } + /** * Sign out method + * @ * @return - A promise resolved if success */ - public async signOut(): Promise { + public async signOut(opts?: SignOutOpts): Promise { try { await this.cleanCachedItems(); } catch (e) { @@ -1097,28 +1121,27 @@ export default class AuthClass { if (this.userPool) { const user = this.userPool.getCurrentUser(); if (user) { - logger.debug('user sign out', user); - user.signOut(); + await this.cognitoIdentitySignOut(opts, user); if (this._cognitoAuthClient) { this._cognitoAuthClient.signOut(); } + } else { + logger.debug('no current Cognito user'); } } else { logger.debug('no Congito User pool'); } - const that = this; - return new Promise(async (resolve, reject) => { - try { - await Credentials.set(null, 'guest'); - } catch (e) { - logger.debug('cannot load guest credentials for unauthenticated user', e); - } finally { - dispatchAuthEvent('signOut', that.user); - that.user = null; - resolve(); - } - }); + + try { + await Credentials.set(null, 'guest'); + } catch (e) { + logger.debug('cannot load guest credentials for unauthenticated user', e); + } finally { + dispatchAuthEvent('signOut', this.user); + this.user = null; + return; + } } private async cleanCachedItems() { diff --git a/packages/auth/src/types/Auth.ts b/packages/auth/src/types/Auth.ts index edb802878ad..0017c75702c 100644 --- a/packages/auth/src/types/Auth.ts +++ b/packages/auth/src/types/Auth.ts @@ -77,3 +77,7 @@ export interface OAuth { export interface ConfirmSignUpOptions { forceAliasCreation?: boolean } + +export interface SignOutOpts { + global?: boolean +}