Repository: parse-community/Parse-SDK-JS
Pull request #963 (2019-10-18 20:38:19)

Missing documentation for Custom Auth
Closes: https://github.com/parse-community/Parse-SDK-JS/issues/934
Addresses: https://github.com/parse-community/Parse-SDK-JS/issues/552

Adds deprecation for _linkWith and _loginWith

I can check the links works after this PR is merged. 

codecov[bot]: # [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/963?src=pr&el=h1) Report
> Merging [#963](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/963?src=pr&el=desc) into [master](https://codecov.io/gh/parse-community/Parse-SDK-JS/commit/7ff9f9295e3e290c0d58f6ecc23685005f65877c?src=pr&el=desc) will **decrease** coverage by `0.05%`.
> The diff coverage is `92.85%`.

[![Impacted file tree graph](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/963/graphs/tree.svg?width=650&token=8nCENM0QHj&height=150&src=pr)](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/963?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #963      +/-   ##
==========================================
- Coverage   91.94%   91.88%   -0.06%     
==========================================
  Files          54       54              
  Lines        5077     5080       +3     
  Branches     1142     1143       +1     
==========================================
  Hits         4668     4668              
- Misses        409      412       +3
```


| [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/963?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/FacebookUtils.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/963/diff?src=pr&el=tree#diff-c3JjL0ZhY2Vib29rVXRpbHMuanM=) | `100% <100%> (ø)` | :arrow_up: |
| [src/AnonymousUtils.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/963/diff?src=pr&el=tree#diff-c3JjL0Fub255bW91c1V0aWxzLmpz) | `100% <100%> (ø)` | :arrow_up: |
| [src/ParseUser.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/963/diff?src=pr&el=tree#diff-c3JjL1BhcnNlVXNlci5qcw==) | `82.61% <87.5%> (-0.57%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/963?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/963?src=pr&el=footer). Last update [7ff9f92...07120dc](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/963?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

dplewis: @rhuanbarreto Anything you want to add?
dplewis: There is documentation in the guide https://docs.parseplatform.org/js/guide/#custom-authentication-module

Working code examples are in [ParseUserTest](https://github.com/parse-community/Parse-SDK-JS/blob/master/integration/test/ParseUserTest.js) and a working server setup [here](https://github.com/parse-community/Parse-SDK-JS/blob/master/integration/server.js#L17).
dplewis: @acinader Perhaps we should add this to the ReadMe since this highly requested?
rhuanbarreto: I think that this will help more the people looking for answers and it’s enough for now. I managed to implement the Keycloak login but I’m using the REST API for now. Soon I will try to commit something showing how is it on Server side.
dplewis: @rhuanbarreto Having another set of eyes always helps as we tend to forget new users have to read through the documentation.

If you can try to use `linkWith` and provide feedback on if these changes are clear. We would greatly appreciate it.

Patch:
diff --git a/README.md b/README.md
index 31566a1..36311de 100644
--- a/README.md
+++ b/README.md
@@ -80,6 +80,10 @@ With Parse SDK 2.0.0, gone are the backbone style callbacks and Parse.Promises.
 
 We have curated a [migration guide](2.0.0.md) that should help you migrate your code.
 
+## 3rd Party Authentications
+
+Parse Server supports many [3rd Party Authenications][3rd-parth-auth]. It is possible to [linkWith][link-with] any 3rd Party Authentication by creating a [custom authentication module][custom-auth-module].
+
 ## Want to ride the bleeding edge?
 
 We recommend using the most recent tagged build published to npm for production. However, you can test not-yet-released versions of the Parse-SDK-JS by referencing specific branches in your `package.json`. For example, to use the master branch:
@@ -116,6 +120,8 @@ of patent rights can be found in the PATENTS file in the same directory.
 -----
 As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code.
  
- [types-parse]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/parse
- 
- [open-collective-link]: https://opencollective.com/parse-server
+[3rd-party-auth]: http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication
+[custom-auth-module]:https://docs.parseplatform.org/js/guide/#custom-authentication-module).
+[link-with]: https://docs.parseplatform.org/js/guide/#linking-users
+[open-collective-link]: https://opencollective.com/parse-server
+[types-parse]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/parse 
diff --git a/src/AnonymousUtils.js b/src/AnonymousUtils.js
index f1dcedd..d034dec 100644
--- a/src/AnonymousUtils.js
+++ b/src/AnonymousUtils.js
@@ -69,7 +69,7 @@ const AnonymousUtils = {
    */
   logIn(options?: RequestOptions) {
     const provider = this._getAuthProvider();
-    return ParseUser._logInWith(provider.getAuthType(), provider.getAuthData(), options);
+    return ParseUser.logInWith(provider.getAuthType(), provider.getAuthData(), options);
   },
 
   /**
@@ -84,7 +84,7 @@ const AnonymousUtils = {
    */
   link(user: ParseUser, options?: RequestOptions) {
     const provider = this._getAuthProvider();
-    return user._linkWith(provider.getAuthType(), provider.getAuthData(), options);
+    return user.linkWith(provider.getAuthType(), provider.getAuthData(), options);
   },
 
   _getAuthProvider() {
diff --git a/src/FacebookUtils.js b/src/FacebookUtils.js
index 85729b9..17ecaac 100644
--- a/src/FacebookUtils.js
+++ b/src/FacebookUtils.js
@@ -170,10 +170,10 @@ const FacebookUtils = {
         );
       }
       requestedPermissions = permissions;
-      return ParseUser._logInWith('facebook', options);
+      return ParseUser.logInWith('facebook', options);
     }
     const authData = { authData: permissions };
-    return ParseUser._logInWith('facebook', authData, options);
+    return ParseUser.logInWith('facebook', authData, options);
   },
 
   /**
@@ -210,10 +210,10 @@ const FacebookUtils = {
         );
       }
       requestedPermissions = permissions;
-      return user._linkWith('facebook', options);
+      return user.linkWith('facebook', options);
     }
     const authData = { authData: permissions };
-    return user._linkWith('facebook', authData, options);
+    return user.linkWith('facebook', authData, options);
   },
 
   /**
diff --git a/src/ParseUser.js b/src/ParseUser.js
index cb5f9b2..406a0c7 100644
--- a/src/ParseUser.js
+++ b/src/ParseUser.js
@@ -74,10 +74,21 @@ class ParseUser extends ParseObject {
   }
 
   /**
-   * Unlike in the Android/iOS SDKs, logInWith is unnecessary, since you can
-   * call linkWith on the user (even if it doesn't exist yet on the server).
+   * Parse allows you to link your users with {@link https://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication 3rd party authentication}, enabling
+   * your users to sign up or log into your application using their existing identities.
+   * Since 2.9.0
+   *
+   * @see {@link https://docs.parseplatform.org/js/guide/#linking-users Linking Users}
+   * @param {String|AuthProvider} provider Name of auth provider or {@link https://parseplatform.org/Parse-SDK-JS/api/master/AuthProvider.html AuthProvider}
+   * @param {Object} options
+   * <ul>
+   *   <li>If provider is string, options is {@link http://docs.parseplatform.org/parse-server/guide/#supported-3rd-party-authentications authData}
+   *   <li>If provider is AuthProvider, options is saveOpts
+   * </ul>
+   * @param {Object} saveOpts useMasterKey / sessionToken
+   * @return {Promise} A promise that is fulfilled with the user is linked
    */
-  _linkWith(provider: any, options: { authData?: AuthData }, saveOpts?: FullOptions = {}): Promise<ParseUser> {
+  linkWith(provider: any, options: { authData?: AuthData }, saveOpts?: FullOptions = {}): Promise<ParseUser> {
     saveOpts.sessionToken = saveOpts.sessionToken || this.getSessionToken() || '';
     let authType;
     if (typeof provider === 'string') {
@@ -118,7 +129,7 @@ class ParseUser extends ParseObject {
           success: (provider, result) => {
             const opts = {};
             opts.authData = result;
-            this._linkWith(provider, opts, saveOpts).then(() => {
+            this.linkWith(provider, opts, saveOpts).then(() => {
               resolve(this);
             }, (error) => {
               reject(error);
@@ -132,6 +143,13 @@ class ParseUser extends ParseObject {
     }
   }
 
+  /**
+   * @deprecated since 2.9.0 see {@link https://parseplatform.org/Parse-SDK-JS/api/master/Parse.User.html#linkWith linkWith}
+   */
+  _linkWith(provider: any, options: { authData?: AuthData }, saveOpts?: FullOptions = {}): Promise<ParseUser> {
+    return this.linkWith(provider, options, saveOpts);
+  }
+
   /**
    * Synchronizes auth data for a provider (e.g. puts the access token in the
    * right place to be used by the Facebook SDK).
@@ -200,7 +218,7 @@ class ParseUser extends ParseObject {
     if (typeof provider === 'string') {
       provider = authProviders[provider];
     }
-    return this._linkWith(provider, { authData: null }, options).then(() => {
+    return this.linkWith(provider, { authData: null }, options).then(() => {
       this._synchronizeAuthData(provider);
       return Promise.resolve(this);
     });
@@ -687,8 +705,13 @@ class ParseUser extends ParseObject {
     return controller.hydrate(userJSON);
   }
 
+  /**
+   * Static version of {@link https://parseplatform.org/Parse-SDK-JS/api/master/Parse.User.html#linkWith linkWith}
+   * @static
+   */
   static logInWith(provider: any, options: { authData?: AuthData }, saveOpts?: FullOptions) {
-    return ParseUser._logInWith(provider, options, saveOpts);
+    const user = new ParseUser();
+    return user.linkWith(provider, options, saveOpts);
   }
 
   /**
@@ -796,6 +819,17 @@ class ParseUser extends ParseObject {
     canUseCurrentUser = false;
   }
 
+  /**
+   * When registering users with {@link https://parseplatform.org/Parse-SDK-JS/api/master/Parse.User.html#linkWith linkWith} a basic auth provider
+   * is automatically created for you.
+   *
+   * For advanced authentication, you can register an Auth provider to
+   * implement custom authentication, deauthentication.
+   *
+   * @see {@link https://parseplatform.org/Parse-SDK-JS/api/master/AuthProvider.html AuthProvider}
+   * @see {@link https://docs.parseplatform.org/js/guide/#custom-authentication-module Custom Authentication Module}
+   * @static
+   */
   static _registerAuthenticationProvider(provider: any) {
     authProviders[provider.getAuthType()] = provider;
     // Synchronize the current user with the auth provider.
@@ -806,9 +840,13 @@ class ParseUser extends ParseObject {
     });
   }
 
+  /**
+   * @deprecated since 2.9.0 see {@link https://parseplatform.org/Parse-SDK-JS/api/master/Parse.User.html#logInWith logInWith}
+   * @static
+   */
   static _logInWith(provider: any, options: { authData?: AuthData }, saveOpts?: FullOptions) {
     const user = new ParseUser();
-    return user._linkWith(provider, options, saveOpts);
+    return user.linkWith(provider, options, saveOpts);
   }
 
   static _clearCache() {
diff --git a/src/interfaces/AuthProvider.js b/src/interfaces/AuthProvider.js
index 433063a..644ec64 100644
--- a/src/interfaces/AuthProvider.js
+++ b/src/interfaces/AuthProvider.js
@@ -1,3 +1,4 @@
+/* eslint no-unused-vars: "off" */
 /**
  * Copyright (c) 2015-present, Parse, LLC.
  * All rights reserved.
@@ -10,28 +11,30 @@
 
 /**
  * Interface declaration for Authentication Providers
+ *
+ * @interface AuthProvider
  */
-export interface AuthProvider {
+export class AuthProvider {
   /**
    * Called when _linkWith isn't passed authData.
    * Handle your own authentication here.
    *
    * @params {Object} options.success(provider, authData) or options.error(provider, error) on completion
    */
-  authenticate(options: any): void,
+  authenticate(options: any): void {}
 
   /**
    * (Optional) Called when service is unlinked.
    * Handle any cleanup here.
    */
-  deauthenticate(): void,
+  deauthenticate(): void {}
 
   /**
    * Unique identifier for this Auth Provider.
    *
    * @return {String} identifier
    */
-  getAuthType(): string,
+  getAuthType(): string {}
 
   /**
    * Called when auth data is syncronized.
@@ -40,5 +43,5 @@ export interface AuthProvider {
    * @params {Object} authData Data used when register provider
    * @return {Boolean} Indicate if service should continue to be linked
    */
-  restoreAuthentication(authData: any): boolean,
+  restoreAuthentication(authData: any): boolean {}
 }


Test patch:
diff --git a/integration/test/ParseUserTest.js b/integration/test/ParseUserTest.js
index f483005..30e50b2 100644
--- a/integration/test/ParseUserTest.js
+++ b/integration/test/ParseUserTest.js
@@ -676,7 +676,7 @@ describe('Parse User', () => {
     user.setUsername('Alice');
     user.setPassword('sekrit');
     await user.signUp();
-    await user._linkWith(provider.getAuthType(), provider.getAuthData());
+    await user.linkWith(provider.getAuthType(), provider.getAuthData());
     expect(user._isLinked(provider)).toBe(true);
     await user._unlinkFrom(provider);
     expect(user._isLinked(provider)).toBe(false);
@@ -689,7 +689,7 @@ describe('Parse User', () => {
     user.setUsername('Alice');
     user.setPassword('sekrit');
     await user.save(null, { useMasterKey: true });
-    await user._linkWith(provider.getAuthType(), provider.getAuthData(), { useMasterKey: true });
+    await user.linkWith(provider.getAuthType(), provider.getAuthData(), { useMasterKey: true });
     expect(user._isLinked(provider)).toBe(true);
     await user._unlinkFrom(provider, { useMasterKey: true });
     expect(user._isLinked(provider)).toBe(false);
@@ -705,7 +705,7 @@ describe('Parse User', () => {
     expect(user.isCurrent()).toBe(false);
 
     const sessionToken = user.getSessionToken();
-    await user._linkWith(provider.getAuthType(), provider.getAuthData(), { sessionToken });
+    await user.linkWith(provider.getAuthType(), provider.getAuthData(), { sessionToken });
     expect(user._isLinked(provider)).toBe(true);
     await user._unlinkFrom(provider, { sessionToken });
     expect(user._isLinked(provider)).toBe(false);
@@ -716,7 +716,7 @@ describe('Parse User', () => {
     user.setUsername('Alice');
     user.setPassword('sekrit');
     await user.save(null, { useMasterKey: true });
-    await user._linkWith(provider.getAuthType(), provider.getAuthData(), { useMasterKey: true });
+    await user.linkWith(provider.getAuthType(), provider.getAuthData(), { useMasterKey: true });
     expect(user._isLinked(provider)).toBe(true);
     expect(user.authenticated()).toBeFalsy();
     Parse.User.enableUnsafeCurrentUser();
@@ -729,7 +729,7 @@ describe('Parse User', () => {
     user.setUsername('Alice');
     user.setPassword('sekrit');
     await user.save(null, { useMasterKey: true });
-    await user._linkWith(provider.getAuthType(), provider.getAuthData());
+    await user.linkWith(provider.getAuthType(), provider.getAuthData());
     expect(user.getSessionToken()).toBeDefined();
   });
 
@@ -758,7 +758,7 @@ describe('Parse User', () => {
     user.setUsername('Alice');
     user.setPassword('sekrit');
     await user.signUp();
-    await user._linkWith(provider.getAuthType(), provider.getAuthData());
+    await user.linkWith(provider.getAuthType(), provider.getAuthData());
     expect(user._isLinked(provider)).toBe(true);
     await user._unlinkFrom(provider);
     expect(user._isLinked(provider)).toBe(false);
@@ -812,7 +812,7 @@ describe('Parse User', () => {
     user.setPassword('sekrit');
     await user.signUp();
 
-    await user._linkWith('twitter', { authData });
+    await user.linkWith('twitter', { authData });
 
     expect(user.get('authData').twitter.id).toBe(authData.id);
     expect(user._isLinked('twitter')).toBe(true);
@@ -836,7 +836,7 @@ describe('Parse User', () => {
     user.setPassword('sekrit');
     await user.signUp();
 
-    await user._linkWith('twitter', { authData });
+    await user.linkWith('twitter', { authData });
     await Parse.FacebookUtils.link(user);
 
     expect(Parse.FacebookUtils.isLinked(user)).toBe(true);
diff --git a/src/__tests__/AnonymousUtils-test.js b/src/__tests__/AnonymousUtils-test.js
index e287251..2ef1abf 100644
--- a/src/__tests__/AnonymousUtils-test.js
+++ b/src/__tests__/AnonymousUtils-test.js
@@ -15,9 +15,9 @@ class MockUser {
     this.attributes = {};
   }
   _isLinked() {}
-  _linkWith() {}
+  linkWith() {}
   static _registerAuthenticationProvider() {}
-  static _logInWith() {}
+  static logInWith() {}
 }
 
 jest.setMock('../ParseUser', MockUser);
@@ -71,18 +71,18 @@ describe('AnonymousUtils', () => {
 
   it('can link user', () => {
     const user = new MockUser();
-    jest.spyOn(user, '_linkWith');
+    jest.spyOn(user, 'linkWith');
     AnonymousUtils.link(user);
-    expect(user._linkWith).toHaveBeenCalledTimes(1);
-    expect(user._linkWith).toHaveBeenCalledWith('anonymous', mockProvider.getAuthData(), undefined);
+    expect(user.linkWith).toHaveBeenCalledTimes(1);
+    expect(user.linkWith).toHaveBeenCalledWith('anonymous', mockProvider.getAuthData(), undefined);
     expect(AnonymousUtils._getAuthProvider).toHaveBeenCalledTimes(1);
   });
 
   it('can login user', () => {
-    jest.spyOn(MockUser, '_logInWith');
+    jest.spyOn(MockUser, 'logInWith');
     AnonymousUtils.logIn();
-    expect(MockUser._logInWith).toHaveBeenCalledTimes(1);
-    expect(MockUser._logInWith).toHaveBeenCalledWith('anonymous', mockProvider.getAuthData(), undefined);
+    expect(MockUser.logInWith).toHaveBeenCalledTimes(1);
+    expect(MockUser.logInWith).toHaveBeenCalledWith('anonymous', mockProvider.getAuthData(), undefined);
     expect(AnonymousUtils._getAuthProvider).toHaveBeenCalledTimes(1);
   });
 });
diff --git a/src/__tests__/FacebookUtils-test.js b/src/__tests__/FacebookUtils-test.js
index 5dd4a53..4dfec40 100644
--- a/src/__tests__/FacebookUtils-test.js
+++ b/src/__tests__/FacebookUtils-test.js
@@ -15,10 +15,10 @@ class MockUser {
     this.attributes = {};
   }
   _isLinked() {}
-  _linkWith() {}
+  linkWith() {}
   _unlinkFrom() {}
   static _registerAuthenticationProvider() {}
-  static _logInWith() {}
+  static logInWith() {}
 }
 
 jest.setMock('../ParseUser', MockUser);
@@ -110,17 +110,17 @@ describe('FacebookUtils', () => {
     const authData = {
       id: '1234'
     };
-    jest.spyOn(user, '_linkWith');
+    jest.spyOn(user, 'linkWith');
     await FacebookUtils.link(user, authData);
-    expect(user._linkWith).toHaveBeenCalledWith('facebook', { authData: { id: '1234' } }, undefined);
+    expect(user.linkWith).toHaveBeenCalledWith('facebook', { authData: { id: '1234' } }, undefined);
   });
 
   it('can link with options', async () => {
     FacebookUtils.init();
     const user = new MockUser();
-    jest.spyOn(user, '_linkWith');
+    jest.spyOn(user, 'linkWith');
     await FacebookUtils.link(user, {}, { useMasterKey: true });
-    expect(user._linkWith).toHaveBeenCalledWith('facebook', { authData: {} }, { useMasterKey: true });
+    expect(user.linkWith).toHaveBeenCalledWith('facebook', { authData: {} }, { useMasterKey: true });
   });
 
   it('can check isLinked', async () => {
@@ -147,23 +147,23 @@ describe('FacebookUtils', () => {
 
   it('can login with permission string', async () => {
     FacebookUtils.init();
-    jest.spyOn(MockUser, '_logInWith');
+    jest.spyOn(MockUser, 'logInWith');
     await FacebookUtils.logIn('public_profile');
-    expect(MockUser._logInWith).toHaveBeenCalledTimes(1);
+    expect(MockUser.logInWith).toHaveBeenCalledTimes(1);
   });
 
   it('can login with authData', async () => {
     FacebookUtils.init();
-    jest.spyOn(MockUser, '_logInWith');
+    jest.spyOn(MockUser, 'logInWith');
     await FacebookUtils.logIn({ id: '1234' });
-    expect(MockUser._logInWith).toHaveBeenCalledTimes(1);
+    expect(MockUser.logInWith).toHaveBeenCalledTimes(1);
   });
 
   it('can login with options', async () => {
     FacebookUtils.init();
-    jest.spyOn(MockUser, '_logInWith');
+    jest.spyOn(MockUser, 'logInWith');
     await FacebookUtils.logIn({}, { useMasterKey: true });
-    expect(MockUser._logInWith).toHaveBeenCalledWith('facebook', { authData: {} }, {useMasterKey: true });
+    expect(MockUser.logInWith).toHaveBeenCalledWith('facebook', { authData: {} }, {useMasterKey: true });
   });
 
   it('provider getAuthType', async () => {
diff --git a/src/__tests__/ParseUser-test.js b/src/__tests__/ParseUser-test.js
index b94e5a0..5dc6939 100644
--- a/src/__tests__/ParseUser-test.js
+++ b/src/__tests__/ParseUser-test.js
@@ -837,17 +837,17 @@ describe('ParseUser', () => {
     const provider = AnonymousUtils._getAuthProvider();
     ParseUser._registerAuthenticationProvider(provider);
     const user = new ParseUser();
-    jest.spyOn(user, '_linkWith');
+    jest.spyOn(user, 'linkWith');
     user._unlinkFrom(provider);
-    expect(user._linkWith).toHaveBeenCalledTimes(1);
-    expect(user._linkWith).toHaveBeenCalledWith(provider, { authData: null }, undefined);
+    expect(user.linkWith).toHaveBeenCalledTimes(1);
+    expect(user.linkWith).toHaveBeenCalledWith(provider, { authData: null }, undefined);
   });
 
   it('can unlink with options', async () => {
     const provider = AnonymousUtils._getAuthProvider();
     ParseUser._registerAuthenticationProvider(provider);
     const user = new ParseUser();
-    jest.spyOn(user, '_linkWith')
+    jest.spyOn(user, 'linkWith')
       .mockImplementationOnce((authProvider, authData, saveOptions) => {
         expect(authProvider).toEqual(provider);
         expect(authData).toEqual({ authData: null});
@@ -855,7 +855,7 @@ describe('ParseUser', () => {
         return Promise.resolve();
       });
     user._unlinkFrom(provider.getAuthType(), { useMasterKey: true });
-    expect(user._linkWith).toHaveBeenCalledTimes(1);
+    expect(user.linkWith).toHaveBeenCalledTimes(1);
   });
 
   it('can destroy anonymous user when login new user', async () => {
@@ -1003,10 +1003,10 @@ describe('ParseUser', () => {
     await user._linkWith('testProvider', { authData: { id: 'test' } });
     expect(user.get('authData')).toEqual({ testProvider: { id: 'test' } });
 
-    jest.spyOn(user, '_linkWith');
+    jest.spyOn(user, 'linkWith');
 
     await user._unlinkFrom('testProvider');
-    const authProvider = user._linkWith.mock.calls[0][0];
+    const authProvider = user.linkWith.mock.calls[0][0];
     expect(authProvider.getAuthType()).toBe('testProvider');
   });
 });