abacritt / angularx-social-login

Social login and authentication module for Angular 17
634 stars 387 forks source link

Google Authentication Deprecation | "Your client application uses libraries for user authentication or authorization that will soon be deprecated." #489

Closed kopyl closed 2 years ago

kopyl commented 2 years ago

I got this warning in the console:

https://imgur.com/a/I3mACzR

Here is the link:

https://developers.google.com/identity/gsi/web/guides/gis-migration

Here is how I log in with Google:

private authService: SocialAuthService,

...

this.authService.initState.subscribe((value: boolean) => {
            this.authService
                .signIn(GoogleLoginProvider.PROVIDER_ID)
                .then((data) => {
                    localStorage.setItem("google_auth", JSON.stringify(data))
                    this.initJWTauth(data)
                })
        })

Could you please fix Google SocialAuthService so we don't need to make any change in the code to make the warning disappear?

viheag21 commented 2 years ago

I have the same error

Octarines commented 2 years ago

I also have this issue and am somewhat confused by the comments in #473 as to whether it is supposed to be resolved?

The merge listed above seems to imply the fix should be in the latest version of this package (though he lists v4.1.0 which is the last version number of the old package location). I currently have installed the latest version of the new package location (i.e. @abacritt/angularx-social-login v1.0.2) and I am still getting the error message in the console.

Is the fix actually in @abacritt/angularx-social-login yet?

A clarification on the version numbering since the package location migration would help here too :)

image

sommmen commented 2 years ago

Hm - so i'm using the version compatible with angular v11: https://www.npmjs.com/package/angularx-social-login/v/3.5.7 And i also have this issue.

@jaibatrik will the migration to the new google library also be moved to the older versions to remain compatble with older angular versions or not?

Jeremybdel commented 2 years ago

Hi ! I'm using angular cli 13.3.5 but got the same error here !

Vigneshprabu-augment commented 2 years ago

Hi, I'm using angular CLI 9.1.12 but got the same error here, Please help me how to fix this issue

jaibatrik commented 2 years ago

Looking for PR to fix it in the lates version at least. It will be great if someone can send a PR for this.

Elya2305 commented 2 years ago

Same issue here

kurpav commented 2 years ago

Are there any updates on this?

Heatmanofurioso commented 2 years ago

I've deployed the changes. They should be present in https://www.npmjs.com/package/@abacritt/angularx-social-login

We had to change the packaging in NPM, so it's now under the @abacritt org

oliverfrost commented 2 years ago

Does new release resolve the issue for someone?

I've downloaded the latest version and here is what I have at app initialisation.

{
details: "You have created a new client application that uses libraries for user authentication or authorization that will soon be deprecated. New clients must use the new libraries instead; existing clients must also migrate before these libraries are deprecated. See the [Migration Guide](https://developers.google.com/identity/gsi/web/guides/gis-migration) for more information."
error: "idpiframe_initialization_failed"
}

Sign In/ Sign Up with Google also fails with an error:

zone.js:1213 Uncaught Error: Uncaught (in promise): Object: {"error":"popup_closed_by_user"}
    at resolvePromise (zone.js:1213:1)
    at zone.js:1120:1
    at abacritt-angularx-social-login.mjs:283:25
    at ZoneDelegate.invoke (zone.js:372:1)
    at Object.onInvoke (core.mjs:25608:1)
    at ZoneDelegate.invoke (zone.js:371:1)
    at Zone.run (zone.js:134:1)
    at zone.js:1276:1
    at ZoneDelegate.invokeTask (zone.js:406:1)
    at Object.onInvokeTask (core.mjs:25595:1)

It opens modal window, allows to enter and submit credentials and then fails.

My setup:

    "@abacritt/angularx-social-login": "^1.1.0",
    "@angular-devkit/build-angular": "13.3.6",
    "@angular/cdk": "13.3.7",
    "@angular/cli": "13.3.6",
    "@angular/common": "13.3.9",
    "@angular/compiler": "13.3.9",
    "@angular/core": "13.3.9",
    "@angular/forms": "13.3.9",
    "@angular/platform-browser": "13.3.9",
    "@angular/platform-browser-dynamic": "13.3.9",
    "@angular/router": "13.3.9",

Nothing unusual. Just imports the SocialLoginModule, registers provider and calls this.authService.signIn(GoogleLoginProvider.PROVIDER_ID);. Just like in a demo project.

AyuDiego commented 2 years ago

Does new release resolve the issue for someone?

I've downloaded the latest version and here is what I have at app initialisation.

{
details: "You have created a new client application that uses libraries for user authentication or authorization that will soon be deprecated. New clients must use the new libraries instead; existing clients must also migrate before these libraries are deprecated. See the [Migration Guide](https://developers.google.com/identity/gsi/web/guides/gis-migration) for more information."
error: "idpiframe_initialization_failed"
}

Sign In/ Sign Up with Google also fails with an error:

zone.js:1213 Uncaught Error: Uncaught (in promise): Object: {"error":"popup_closed_by_user"}
    at resolvePromise (zone.js:1213:1)
    at zone.js:1120:1
    at abacritt-angularx-social-login.mjs:283:25
    at ZoneDelegate.invoke (zone.js:372:1)
    at Object.onInvoke (core.mjs:25608:1)
    at ZoneDelegate.invoke (zone.js:371:1)
    at Zone.run (zone.js:134:1)
    at zone.js:1276:1
    at ZoneDelegate.invokeTask (zone.js:406:1)
    at Object.onInvokeTask (core.mjs:25595:1)

It opens modal window, allows to enter and submit credentials and then fails.

My setup:

    "@abacritt/angularx-social-login": "^1.1.0",
    "@angular-devkit/build-angular": "13.3.6",
    "@angular/cdk": "13.3.7",
    "@angular/cli": "13.3.6",
    "@angular/common": "13.3.9",
    "@angular/compiler": "13.3.9",
    "@angular/core": "13.3.9",
    "@angular/forms": "13.3.9",
    "@angular/platform-browser": "13.3.9",
    "@angular/platform-browser-dynamic": "13.3.9",
    "@angular/router": "13.3.9",

Nothing unusual. Just imports the SocialLoginModule, registers provider and calls this.authService.signIn(GoogleLoginProvider.PROVIDER_ID);. Just like in a demo project.

happens the same on me :C

Tim-mhn commented 2 years ago

Happens to me as well, even though I have downloaded the latest version of the package.

I went into the code of the google-login-provider.ts and it still uses the old JS script https://apis.google.com/js/api.js. It should be https://accounts.google.com/gsi/client instead.

The flow has changed a lot with the new update. I was able to update the initialize and signIn functions to retrieve the accessToken (see below). However, it seems much harder to get the idToken and, thus, the profile information (name, email, emailVerified ...). It appears that Google is forcing to use their own button component which comes inside an iframe, making it very hard to have a pure JS solution.

Basic implementation to get the accessToken

initialize(): Promise<void> {
    return new Promise((resolve, reject) => {
      try {
      this.loadScript(
          GoogleLoginProvider.PROVIDER_ID,
          'https://accounts.google.com/gsi/client',
          () => {
            this.client = google.accounts.oauth2.initTokenClient({
              ...this.initOptions,
              client_id: this.clientId,
              callback: (r) => {
                console.log(r);
              },
              // allowed_parent_origin: 'http://localhost:4200' // had to add this to make it work on localhost for the moment 
            });
            resolve();

     } catch (err) {
          reject(err);
    }
}

 signIn(signInOptions?: any): Promise<SocialUser> {
    const options = { ...this.initOptions, ...signInOptions };
    return new Promise((resolve, reject) => {
      this.client.callback = (response) => {
        // response  has the following shape 
        // { access_token, authuser, expires_in, prompt, provider, scope, token_type } 
        console.log(response)
        resolve()
      };
      this.client.requestAccessToken();
})
}

NB I even tried to create an invisible google button and click it from JS but I'm unauthorized to do this since it's inside an iframe :/

NB2 There is a temporary trick to make newly created apps with the deprecated Google Sign-In JS Library. You can add any string in plugin_name in the options. As it says here, _New Client IDs created before July 29th, 2022 can set plugin_name to enable use of the Google Platform Library_ and the library will be unavailable from March 31, 2023.

To be used like this

 gapi.client.init({
         client_id: this.clientId,
         plugin_name: 'put anything here',
         ...otherOptions
              })

Some useful links

JS API Google Identity Services migration

Heatmanofurioso commented 2 years ago

@Tim-mhn Thanks for your work on this.

I haven't explored the new API myself, but would you be okay with me "or someone from the community" to adapt this and update the lib with it?

AyuDiego commented 2 years ago

W/e. I Hope u find a solution. I am busy right now with a personal project, could you help me? It really makes me silly to get data from a service and paint it in a form but I'm a novice at this and I don't have anyone to help me. Regards,

Diego Del Barrio Ayuso

El jue, 26 may 2022 a las 11:57, Tim Meehan @.***>) escribió:

Happens to me as well, even though I have downloaded the latest version of the package.

I went into the code of the google-login-provider.ts and it still uses the old JS script https://apis.google.com/js/api.js https://apis.google.com/js/api.js. It should be https://accounts.google.com/gsi/client https://accounts.google.com/gsi/client instead.

The flow has changed a lot with the new update. I was able to update the initialize and signIn functions to retrieve the accessToken (see below). However, it seems much harder to get the tokenId and, thus, the profile information (name, email, emailVerified ...). It appears that Google is forcing to use their own button component which comes inside an iframe, making it very hard to have a pure JS solution.

Basic implementation to get the accessToken

initialize(): Promise { return new Promise((resolve, reject) => { try { this.loadScript( GoogleLoginProvider.PROVIDER_ID, 'https://accounts.google.com/gsi/client', () => { this.client = google.accounts.oauth2.initTokenClient({ ...this.initOptions, client_id: this.clientId, callback: (r) => { console.log(r); }, // allowed_parent_origin: 'http://localhost:4200' // had to add this to make it work on localhost for the moment }); resolve();

 } catch (err) {
      reject(err);
}

}

signIn(signInOptions?: any): Promise { const options = { ...this.initOptions, ...signInOptions }; return new Promise((resolve, reject) => { this.client.callback = (response) => { // response has the following shape // { access_token, authuser, expires_in, prompt, provider, scope, token_type } console.log(response) resolve() }; this.client.requestAccessToken(); }) }

NB: I even tried to create an invisible google button and click it from JS but I'm unauthorized to do this since it's inside an iframe :/

Some useful links

JS API https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.initialize Google Identity Services migration https://developers.google.com/identity/oauth2/web/guides/migration-to-gis#implicit-flow_2

— Reply to this email directly, view it on GitHub https://github.com/abacritt/angularx-social-login/issues/489#issuecomment-1138364011, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALVI6Y3NNZHOFGIGWLVWX63VL5DIVANCNFSM5TR6DSUQ . You are receiving this because you commented.Message ID: @.***>

ShemiNechmad commented 2 years ago

Hi there, working with Angular 8 version and angularx-social-login 2.2.1. Having the same issue. Would like to know if anyone has a professional thought of what is possible to do to solve that. Thank you

Tim-mhn commented 2 years ago

I have been working on it since yesterday. I was able to implement a JS-only solution and retrieve the user's profile information (name, email ...) and an authorization token. But i haven't been able to get the id token, which might be necessary for some users of this library (it's the case for our team). I will keep you updated if i make some progress next week.

In the mean time, you can always add plugin_name: "any string you want" to the options as mentionned above, it will temporarily do the trick

AyuDiego commented 2 years ago

Awesome, thanks!

El El vie, 27 may 2022 a las 14:02, Tim Meehan @.***> escribió:

I have been working on it since yesterday. I was able to implement a JS-only solution and retrieve the user's profile information (name, email ...) And a authorization token But i havent been able to get the id token, which might be necessary for some users of this library (it's the case for us).

In the mean time, you can always add plugin_name: "any string you want" to the options as mentionnés above, it will temporarily do the trick

— Reply to this email directly, view it on GitHub https://github.com/abacritt/angularx-social-login/issues/489#issuecomment-1139553466, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALVI6Y76X42EXRAHALQ4F3DVMC2UBANCNFSM5TR6DSUQ . You are receiving this because you commented.Message ID: @.***>

bartromgens commented 2 years ago

@Tim-mhn Thanks for your efforts. I also need the id token. Seems we have to use the Sign in with Google API.

Authentication and authorization have been split into two APIs in the new library you linked. The google.accounts.oauth2.initTokenClient API only returns the access token for calling Google APIs. If you need an id_token, you should use the Sign in with Google API instead. - source

But that indeed seems to require the renderButton call to get the token response. Something like this:

    google.accounts.id.initialize({
      client_id: '',
      callback: handleCredentialResponse,
      allowed_parent_origin: 'http://localhost:4200'
    });
    google.accounts.id.renderButton(parentElement);

Which indeed is not what we want in this SocialAuthService.signIn interface.

I currently see 3 options:

Tim-mhn commented 2 years ago

@Tim-mhn Thanks for your efforts. I also need the id token. Seems we have to use the Sign in with Google API.

Authentication and authorization have been split into two APIs in the new library you linked. The google.accounts.oauth2.initTokenClient API only returns the access token for calling Google APIs. If you need an id_token, you should use the Sign in with Google API instead. - source

But that indeed seems to require the renderButton call to get the token response. Something like this:

    google.accounts.id.initialize({
      client_id: '',
      callback: handleCredentialResponse,
      allowed_parent_origin: 'http://localhost:4200'
    });
    google.accounts.id.renderButton(parentElement);

Which indeed is not what we want in this SocialAuthService.signIn interface.

I currently see 3 options:

* Use some trickery with clicking a hidden button somehow

* Find some undocumented way to do the same in pure javascript

* Switch to the official Google Button, which breaks the existing concept/interface

Thanks for your feedback. I am under the same impression :/

bartromgens commented 2 years ago

I can imagine it is impossible to click the iframe embedded button. Not my expertise though.

Getting an id token in a different way using the OAuth2 library is probably discouraged because this requires a correct (secure) implementation:

This document describes how to perform the server flow for authenticating the user. The implicit flow is significantly more complicated because of security risks in handling and using tokens on the client side. If you need to implement an implicit flow, we highly recommend using Google Sign-In.

The Microsoft library implements client side Authorization Code Flow with PKCE:

This version of the library uses the OAuth 2.0 Authorization Code Flow with PKCE.

Google doesn't seem to offer the same.

If we need to build our custom implementation of the implicit flow (or better the Authorization Code Flow with PKCE, if possible) with the Google API, we need to be careful to do it securely. I understand the basics of the authentication flows and some vulnerabilities, but this is not my expertise. I can see that the Google button is the more secure way.

Edit: Google does support PKCE with a code_challenge url parameter and a code_verifier, see https://developers.google.com/identity/protocols/oauth2/native-app#step1-code-verifier

I think this can be used to get a code with a request to https://accounts.google.com/o/oauth2/v2/auth and the use the code to get a id token from https://oauth2.googleapis.com/token. This just doesn't seem to be included in the Google library.

It would be a generic OpenID Connect Authorization Code Flow with PKCE.

Tim-mhn commented 2 years ago

Edit: Google does support PKCE with a code_challenge url parameter and a code_verifier, see https://developers.google.com/identity/protocols/oauth2/native-app#step1-code-verifier

I think this can be used to get a code with a request to https://accounts.google.com/o/oauth2/v2/auth and the use the code to get a id token from https://oauth2.googleapis.com/token. This just doesn't seem to be included in the Google library.

It would be a generic OpenID Connect Authorization Code Flow with PKCE.

I'm not sure if this could work since you need to pass in the client_secret to the oauth2.googleapis.com/token request, that doesn't seem like a safe solution :(

On a side-note, this is the code that was able to come up with to get the user's information using the openidconnect.googleapis.com/v1/userinfo endpoint

initialize() {
   return new Promise((resolve, reject) => {
      this.loadScript(
          GoogleLoginProvider.PROVIDER_ID,
          'https://accounts.google.com/gsi/client',
          () => {
            this.client = google.accounts.oauth2.initTokenClient({
              client_id: this.clientId,
              scope: 'openid email profile',
              callback: (tokenResponse) => {},
              allowed_parent_origin: 'http://localhost:4200',
            });
       })

      resolve();
}

signIn() {
   return new Promise((resolve, reject) => {

      // client.callback is called after client.requestAccessToken completes with its response ({ access_token} object)
      this.client.callback = (tokenResponse) => {
         const { access_token } = tokenResponse;
         var xhr = new XMLHttpRequest();
         xhr.addEventListener('load', (e) => {
          const user = this.getUserProfile(JSON.parse(xhr.response));
          console.log(user);
          resolve(user);
        });
        xhr.addEventListener('error', (err) => {
          console.error('error from request', err);
          reject(err);
        });
        xhr.open('GET', 'https://openidconnect.googleapis.com/v1/userinfo');
        xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
        xhr.send();
   }

   this.client.requestAccessToken();

}

 private getUserProfile(userInfo: {
    email: string;
    email_verified: boolean;
    family_name: string;
    given_name: string;
    locale: string;
    name: string;
    picture: string;
    sub: string;
  }): SocialUser {

    const user = new SocialUser();
    user.name = userInfo.name;
    user.email = userInfo.email;
    user.photoUrl = userInfo.picture;
    user.firstName = userInfo.given_name;
    user.lastName = userInfo.family_name;
    return user;

}
bartromgens commented 2 years ago

I'm not sure if this could work since you need to pass in the client_secret to the oauth2.googleapis.com/token request, that doesn't seem like a safe solution :(

Using the client_secret on the client side is indeed not a good idea.

Jin-K commented 2 years ago

On a side-note, this is the code that was able to come up with to get the user's information using the openidconnect.googleapis.com/v1/userinfo endpoint

initialize() {
   return new Promise((resolve, reject) => {
      this.loadScript(
          GoogleLoginProvider.PROVIDER_ID,
          'https://accounts.google.com/gsi/client',
          () => {
            this.client = google.accounts.oauth2.initTokenClient({
              client_id: this.clientId,
              scope: 'openid email profile',
              callback: (tokenResponse) => {},
              allowed_parent_origin: 'http://localhost:4200',
            });
       })

      resolve();
}

signIn() {
   return new Promise((resolve, reject) => {

      // client.callback is called after client.requestAccessToken completes with its response ({ access_token} object)
      this.client.callback = (tokenResponse) => {
         const { access_token } = tokenResponse;
         var xhr = new XMLHttpRequest();
         xhr.addEventListener('load', (e) => {
          const user = this.getUserProfile(JSON.parse(xhr.response));
          console.log(user);
          resolve(user);
        });
        xhr.addEventListener('error', (err) => {
          console.error('error from request', err);
          reject(err);
        });
        xhr.open('GET', 'https://openidconnect.googleapis.com/v1/userinfo');
        xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
        xhr.send();
   }

   this.client.requestAccessToken();

}

 private getUserProfile(userInfo: {
    email: string;
    email_verified: boolean;
    family_name: string;
    given_name: string;
    locale: string;
    name: string;
    picture: string;
    sub: string;
  }): SocialUser {

    const user = new SocialUser();
    user.name = userInfo.name;
    user.email = userInfo.email;
    user.photoUrl = userInfo.picture;
    user.firstName = userInfo.given_name;
    user.lastName = userInfo.family_name;
    return user;

}

Honestly I think this is the exact way to go (for implicit authorization) if you want to preserve the current custom button, otherwise it should be their button.

They say it's ok to call the OAuth 2.0 API endpoints here

ShemiNechmad commented 2 years ago

Meantime, if anybody needs it, here is a repository with an example of Google Sign In with Angular (any version should work) and Google Identity, directly. https://github.com/ShemiNechmad/GoogleSignInAngular

Heatmanofurioso commented 2 years ago

Thanks for the repo @ShemiNechmad I'm going to try and work on an updated implementation this weekend, and update the lib if no one proposes a solution before that

Jin-K commented 2 years ago

Hi @Heatmanofurioso I reopened that PR I closed some days ago. #507 I worked a bit more on it and now it uses the auto-login & auto-logout features of the Google Identity library. The only "problem" is the button generated by the lib that seems impossible to avoid.

Can anyone propose Pull Requests in this repo ? Or should we be listed as contributor first ?

Heatmanofurioso commented 2 years ago

I believe you need to be a contributor @Jin-K And I'm not the owner. I'm just a user, who decided to try and maintain it, and got added as a contributor. "Been doing a bad job at it due to lack of time.. sorry"

But you can just use that PR, and I can effectively accept it if we agree on it

edoremo00 commented 2 years ago

@ShemiNechmad thanks for your help man. Do you know how can I ask user to grant additional permissions to read from their calendar for example? (scopes) in the previous version of the google library I used to be able to that. I found that in the gis library there are two methods in which you can pass a scope parameter, however they don't return a valid JWT token. these methods are : google.accounts.oauth2.initTokenClient and google.accounts.oauth2.initCodeClient. the problem is that I need a JWT token to pass to my backend in order to validate it with a .NET library made by Google which is Google.Apis.Auth package.

Jin-K commented 2 years ago

@edoremo00 google.accounts.oauth2.initTokenClient & google.accounts.oauth2.initCodeClient are 2 methods for the authorization flow, they don't give you a jwt token but an access token to use for their APIs.

to get a jwt token that you can decode by yourself later, you need to call google.accounts.id.initialize() first with a callback that you pass, then google.accounts.id.renderButton(): That will generate a button and when you click on it, some magic happens and your passed callback should then be invoked.

ref: https://developers.google.com/identity/gsi/web/guides/overview

I strongly recommend to install @types/google.accounts as devDependency to benefit of the documentation on the Google Identity library

Tim-mhn commented 2 years ago

Meantime, if anybody needs it, here is a repository with an example of Google Sign In with Angular (any version should work) and Google Identity, directly. https://github.com/ShemiNechmad/GoogleSignInAngular

@edoremo00 you can check out this repo, you have a working html-based example. The crendential returned in the callback (see handleCredentialResponse ) is a JW Token

edoremo00 commented 2 years ago

@Tim-mhn thank you for the repo. I used it however the problem remains. the credential returned is a JWT token but you can't request scopes for using a particular API. passing that JWT to a Google API will result in a 401 unauthorized as you didn't request the scope for it but you just made a Login. Previously you were able to request additional scopes in the login phase.

edoremo00 commented 2 years ago

@Jin-K so now it's a two step thing? first I need to log in the user and than if I want to use a Google API, like Calendar I need to call either google.accounts.oauth2.initTokenClient or google.accounts.oauth2.initCodeClient for using the Api? seems like a downgrade to me. in the previous version(GAPI) you were able to do Login and request for additional scopes in one method

Jin-K commented 2 years ago

@Jin-K so now it's a two step thing? first I need to log in the user and than if I want to use a Google API, like Calendar I need to call either google.accounts.oauth2.initTokenClient or google.accounts.oauth2.initCodeClient for using the Api? seems like a downgrade to me. in the previous version(GAPI) you were able to do Login and request for additional scopes in one method

It's a downgrade I agree 😥

edoremo00 commented 2 years ago

@Jin-K do you know the difference between initTokenclient and initCodeclient?

Jin-K commented 2 years ago

@edoremo00 I already replied that, and I'm not working for Google. I also can't understand why the made it so complex to integrate 2 of their own products.

If you want to get both at the same time and avoid their iframed button, you should try what @Tim-mhn proposes: getting the access token first with initTokenClient, and immediatelly get the id token via a http request.

Heatmanofurioso commented 2 years ago

@abacritt/angularx-social-login:1.2.0 has been deployed.

The functionality has slightly changed, but it should be working fine. We're going to try and work on adding a new Google Provider without the button, and that will later be added to the lib.

If there's any issues further one, we can always reopen this issue