Will
Will

Reputation: 2880

Linking accounts in AngularFire2

I'm handling both anonymous and Google accounts using AngularFire2. If a user logs in using a Google account, I'd like to convert their anonymous account to their permanent (Google) account, so they can keep using the app seamlessly.

It seems easy enough using the Firebase API, but I'm not seeing the ability to do this in AngularFire2.

For Firebase, you get the AuthCredential for the new auth provider and then use the link method to convert the account:

var credential = firebase.auth.GoogleAuthProvider.credential(
  googleUser.getAuthResponse().id_token);

auth.currentUser.link(credential).then(function(user) {
  console.log("Anonymous account successfully upgraded", user);
}, function(error) {
  console.log("Error upgrading anonymous account", error);
});

Is this possible in AngularFire2?

Upvotes: 3

Views: 1560

Answers (4)

Joosep Parts
Joosep Parts

Reputation: 6225

Thrown if there already exists an account with the email address asserted by the credential. Resolve this by calling firebase.auth.Auth.fetchSignInMethodsForEmail with the error.email and then asking the user to sign in using one of the returned providers. Once the user is signed in, the original credential retrieved from the error.credential can be linked to the user with firebase.User.linkWithCredential to prevent the user from signing in again to the original provider via popup or redirect. If you are using redirects for sign in, save the credential in session storage and then retrieve on redirect and repopulate the credential using for example firebase.auth.GoogleAuthProvider.credential depending on the credential provider id and complete the link.

A good solution would be to ➡️ link multiple auth providers to an account. User can signup/signin with any method and we will authenticate them and update their records. So the same user can log in with multiple auth providers.

 // Sign in with Google
  GoogleAuth() {
    return this.authLogin(new GoogleAuthProvider()).then(() => {});
  }

  // Sign in with GitHub
  GithubAuth() {
    return this.authLogin(new GithubAuthProvider()).then(() => {});
  }

  // Auth logic to run auth providers
  authLogin(provider: any) {
    return this.afAuth
               .signInWithPopup(provider)
               .then((result) => {
                 this.setUserData(result.user).then(() => {
                   this.router.navigate(['dashboard']);
                 });
               })
               .catch(error => {

                 const code = error.code;
                 const credential = error.credential;

                 if (code === 'auth/account-exists-with-different-credential') {
                   // Get other Auth providers user has used before (e.g google.com)
                   this.afAuth.fetchSignInMethodsForEmail(error.email).then(result => {
                     const provider = this.getAuthProvider(result[0]);
                     // Log in the user with other provider used before
                     this.authLogin(provider).then(result => {
                       this.afAuth.authState.pipe(take(1)).subscribe(user => {
                         if (user) {
                           user.linkWithCredential(credential).then(() => {
                             console.log('Credential linked successfully: ', credential);
                           });
                         }
                       });
                     });
                   });
                 }
                 
               });
  }

More on this over here: https://medium.com/p/8a0f192458f8

Upvotes: 0

JeffD23
JeffD23

Reputation: 9298

Full example in AngularFire2 v4. It will upgrade an anonymous user to the Google provider, keeping the same auth UID.

import { Component, OnInit } from '@angular/core';
import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.scss']
})
export class UserComponent implements OnInit {

  user: Observable<firebase.User>;

  constructor(private afAuth: AngularFireAuth) { }

  ngOnInit() {
    this.user = this.afAuth.authState;
  }

  anonymousLogin() {
    return this.afAuth.auth.signInAnonymously()
  }

  anonymousUpgrade() {
    const provider = new firebase.auth.GoogleAuthProvider()
    firebase.auth().currentUser.linkWithPopup(provider)
  }


}

Upvotes: 1

Coşkun Deniz
Coşkun Deniz

Reputation: 2279

here is a working example;

import * as firebase from 'firebase':


upgradeAnonymous() {

    let credential = new firebase.auth.GoogleAuthProvider();

    firebase.auth().currentUser.linkWithPopup(credential).then(function(user) {
       console.log("Anonymous account successfully upgraded", user);
      }, function(error) {
        console.log("Error upgrading anonymous account", error);
      })
} 

Upvotes: 0

cartant
cartant

Reputation: 58400

There should be no reason you cannot use the underlying Firebase API, as the Firebase App that's created by AngularFire2 is made available for injection.

You can use the injected app to access any methods of the auth() (or storage()) instances that are not exposed by AngularFire2.

For the purposes of an example, you could inject it into an app component like this:

import { Component, Inject } from "@angular/core";
import { AngularFire, FirebaseApp } from "angularfire2";
import * as firebase from "firebase";

@Component({
  selector: "app",
  template: ...
})
export class AppComponent {

  constructor(
    // Inject AngularFire2:
    private angularFire: AngularFire,
    // Inject the Firebase App instance:
    @Inject(FirebaseApp) private firebaseApp: firebase.app.App
  ) {

    // Perform some sort of login.

    ...

    angularFire.auth.subscribe((state) => {

      // AngularFire2's auth observable will emit when the authentication
      // state changes and if the state is non-null, there will be a
      // current user. At this point, you should be able to do with the
      // injected app what it was you were doing with the SDK.

      if (state) {

        var credential = firebase.auth
          .GoogleAuthProvider
          .credential(googleUser.getAuthResponse().id_token);

        firebaseApp.auth()
          .currentUser
          .link(credential)
          .then((user) => {
            console.log("Anonymous account successfully upgraded", user);
          })
          .catch((error) => {
            console.log("Error upgrading anonymous account", error);
          });
      }
    });
  }
}

You don't have to use the auth observable; you could also put the code into a promise chain using the promise returned by AngularFire2's auth.login method.

As noted in the comments below, the GoogleAuthProvider class is in the firebase.app namespace (not the app instance). Also, as the current user is made available by AngularFire2 in the (confusingly named) auth property of the FirebaseAuthState, you don't need to inject the app instance to do what you want to do with the Google provider. You just need the firebase import:

this.angularFire.auth.login({
  email: "[email protected]",
  password: "password",
})
.then((authState: FirebaseAuthState) => {
  authState.auth.linkWithRedirect(new firebase.auth.GoogleAuthProvider());
});

Upvotes: 3

Related Questions