Implement Sign in with Apple in Ionic 4 application - Part 2

October 2019

We have seen that to implement sign in with Apple we have a lot of things to configure before.

  1. Configure our apple developer account
  2. Configure our Django backend to let apple verify our domain
  3. Configure our Django backend to let apple send user information
  4. Configure our Django backend to search and retrieve user information

Now we will focus on the Ionic implementation code.

Sign in with Apple into your Ionic 4 application

First thing to do which is obvious, is to add a "Sign in with Apple" button on our ionic register page.
You can use an image or create a standard dark button like this:

<ion-button mode="md" expand="block" color="dark" (click)="doAppleLogin()">
          <ion-icon slot="start" class="sw-Apple"></ion-icon> Continuer avec Apple
        </ion-button>

In the click action, we define a doAppleLogin() method, so let's code this method

doAppleLogin() {
this.userManager.loginWithApple=true
let state = Math.random().toString(36);
this.userManager.loginWithApple_state = state;
let redirectUri = encodeURI("https://backend.swiitchvtc.com/api/signin_with_apple/")
let urlParams = "?client_id=com.swiitch.webapp&redirect_uri="+redirectUri+"&response_type=code%20id_token&scope=name%20email&response_mode=form_post&state="+state
let url = "https://appleid.apple.com/auth/authorize"+ urlParams;
let target = "_system"
this.inAppBrowser.create(url, target, "location=no,zoom=no")
}
  1. First we use our UserManagerProviderService to set a flag on it, to keep the trace that we are asking the Apple sign in
  2. Then we generate a random state parameter and save it also in our UserManagerProviderService
  3. We set the params for the Apple auth url:
    • client_id: The one we configured as services id on our developer account (com.swiitch.webapp)
    • redirect_uri: The endpoint we added in our Django backend and configured on our developer account
    • response_type: Please provide code and id_token otherwise you will received an error from Apple server
    • scope:¬†We want the name and email of the user
    • response_mode: form_post (since we want to receive parameters as a post request)
    • state: Our generated identifier that we will use to retrieve the information sent by Apple
  4. We launch a new browser (using ionic InAppBrowser plugin) outside our ionic application by calling the url we just constructed

If everything is ok, your user should be redirected in a browser and next actions will be managed by Apple. You should see a webpage for "Sign in with Apple" and then a popup asking you to identicate.

As you can see when sign in the user can choose to share his real email or to create a fake email that will be manage by Apple.
These values (email and name) are those that you will receive.

At this stage, if everything goes well (meaning developer account is setup successfully and same for our Django backend), we should have a new line in our database in our AppleSignin model and our application user should see a page in his browser asking me to go back to the Ionic application.

If the user goes back to the Ionic application, we should be able to detect it and do some appropriate actions to react to this event. Best place to do this, is in our app.component.ts file. We will check if application is resume from background and if it is because of an apple sign in event (remember the flag we set previously in our UserManagerProviderService):

 //Subscribe on resume
      this.platform.resume.subscribe(() => {
        console.log("===========================")
        console.log("Resume from background ?") 
        if (this.userManager.loginWithApple){
          console.log("loginFromApple "+this.userManager.loginWithApple)
          console.log("loginFromAppleState "+this.userManager.loginWithApple_state)
          this.userManager.checkAppleLogin().then((success)=>{
            if (success){
              this.authentificationService.authenticationState.next(true)
              this.userConnected=true;
              this.authentified()
              this.task = setInterval(() => {
                this.checkAPIAndMessage();
              }, 10000);
            }
          })
        } 
      });

When platform is resuming (meaning coming back from background state), we check if it is because of an apple signin event by checking our previously set flag.
if so, we call a method this.userManager.checkAppleLogin() and since it is a promise, we analyse the status when returned.
On success, our user is authenticated and we can continue the process in our application, such as going to home page, ... (it's up to you).

Let's finish this tutorial by implementing the most important method checkAppleLogin() which will get the user information from our backend and then register the user (if it doesn't already exists) or login the user.

checkAppleLogin(){
    return new Promise(resolve => {
    this.apiService.showLoading()
    this.apiService.searchAppleSignin(this.loginWithApple_state).subscribe((results)=>{
      this.apiService.stopLoading()
      this.loginWithApple=false;
      console.log(results)
      if (results){
        let data = results["results"]
        if (data){
          let firstFind = data[0]
          let id_token = firstFind["token"]
          let jsonUser= firstFind["jsonUser"]
          //parse json
          let jsonConverted = JSON.parse(jsonUser)
          let email=jsonConverted["email"]
          let name = jsonConverted["name"]
          if (email){
            let firstName = name["firstName"]
            // create profil and login 
            let connectionType = 0;
            if (this.platform.is("ios")){
              connectionType = 0
            }
            else{
              connectionType = 1
            }
            let params = {
              "email":email,
              "firstName":firstName,
              "password": CryptoJS.SHA256(name).toString(CryptoJS.enc.Hex),
              "connectionType":connectionType,
              "appleId":id_token
            }
            this.apiService.createUser(params).subscribe((result) => {
              this.apiService.stopLoading();
              if (result) {
                this.setUser(result)
                resolve(true)
              }
              else {
                this.apiService.showError("Désolé une erreur technique est survenue, impossible de créer votre compte")
                resolve(false)
              }
            })
          }
          else{
            //Search existing User
            this.apiService.showLoading()
            this.apiService.findUserByAppleId(id_token).subscribe((results)=>{
              console.log(results)
              this.apiService.stopLoading(); 
                if (results){
                  let count = results["count"]
                  console.log("Count "+count)
                  if (count==0){
                    this.apiService.showMessage("Désolé","Identification Apple échouée")
                    resolve(false)
                  }
                  else{
                    let result = results["results"][0]
		    this.setUser(result)
                    resolve(true)
                  }
                }
            })
          }
        }
        else{
          this.apiService.showMessage("Désolé","Erreur technique, identification Apple impossible")
          resolve(false)
        }
      }
      else{
        this.apiService.showMessage("Désolé","Erreur technique, identification Apple impossible")
        resolve(false)
      }
    })
    })
  }

This method is implemented in our UserManagerProviderService so we can get directly the state identifier that we created earlier to asks our django backend to retrieve the information for this parameter. This is the goal of the method this.apiService.searchAppleSignin(this.loginWithApple_state)

searchAppleSignin(state) {
        const options = {
            headers: new HttpHeaders({
                'Authorization': 'Bearer ' + this.tokenSSO,
                'Content-Type': 'application/json'
            })
        };
        return Observable.create(observer => {
            // At this point make a request to your backend to make a real check!
            console.log("on appelle BACKEND encoded url " + this.getAppleSignInUrl);
            this.http.get(this.getAppleSignInUrl+"?state="+state, options)
                .pipe(retry(1))
                .subscribe(res => {
                    observer.next(res);
                    observer.complete();
                }, error => {
                    observer.next();
                    observer.complete();
                    console.log(error);// Error getting the data
                });
        });
    }

The promise will return a results object. If this object is empty, something goes wrong and we display an error message. (Apple sign in has failed or an error occurs in the django backend)
If we have a results object, we extract the data located in the results json field. (Please remember we are getting values from Django Rest API which return a specific json format)
Then from our data variable, we get the first index object since data is an array of values sorted by descending creation date.

Then we get the id_token which is the unique apple user id
and the jsonUser which contains (or not) the user informations (email,name).
So we parse the jsonUser and check if an email is provided.
If so, we have the user information from Apple, and we can create our user in our backend (register him). Don't forget to save his unique identifier id (id_token) because we will need it to check when a user already exists with this token.

If not provided, it means the user had already signin once into our application (if he logs out and tries to login again), and so we try to get our user by searching by his unique identifier (id_token). If we find him, we can login him to our application, otherwise we need to display a login error message.

And that's it for sign in with Apple inside a Ionic application using a Django backend. Hope you enjoy it.

UPDATE: a new tutorial is now available to explain how to use the native API to sign in with Apple tutorial