Saltar al contenido

Inicio de sesión de Google en Angular 7 con .NET Core API

Solución:

El problema parece ser que, aunque el servidor está enviando una respuesta 302 (redirección de URL), Angular está haciendo una XMLHttpRequest, no está redireccionando. Hay más personas que tienen este problema …

Para mí, intentar interceptar la respuesta en la interfaz para hacer una redirección manual o cambiar el código de respuesta en el servidor (es una respuesta de ‘Desafío’ …) no funcionó.

Entonces, lo que hice para que funcionara fue cambiar en Angular la ubicación de la ventana al servicio de backend para que el navegador pueda administrar la respuesta y realizar la redirección correctamente.

NOTA: Al final de la publicación explico una solución más sencilla para aplicaciones SPA sin el uso de cookies o autenticación AspNetCore.

El flujo completo sería este:

(1) Angular establece la ubicación del navegador en la API -> (2) La API envía la respuesta 302 -> (3) El navegador redirige a Google -> (4) Google devuelve los datos del usuario como una cookie a la API -> (5) La API devuelve JWT token -> (6) Token de uso angular

1.- Angular establece la ubicación del navegador en la API. Pasamos el proveedor y la URL de retorno donde queremos que la API devuelva el token JWT cuando el proceso haya finalizado.

import { DOCUMENT } from '@angular/common';
...
 constructor(@Inject(DOCUMENT) private document: Document, ...) { }
...
  signInExternalLocation() {
    let provider="provider=Google";
    let returnUrl="returnUrl=" + this.document.location.origin + '/register/external';

    this.document.location.href = APISecurityRoutes.authRoutes.signinexternal() + '?' + provider + '&' + returnUrl;
  }

2.- API envía respuesta 302 Challenge. Creamos la redirección con el proveedor y la URL donde queremos que Google nos devuelva la llamada.

// GET: api/auth/signinexternal
[HttpGet("signinexternal")]
public IActionResult SigninExternal(string provider, string returnUrl)
{
    // Request a redirect to the external login provider.
    string redirectUrl = Url.Action(nameof(SigninExternalCallback), "Auth", new { returnUrl });
    AuthenticationProperties properties = _signInMgr.ConfigureExternalAuthenticationProperties(provider, redirectUrl);

    return Challenge(properties, provider);
}

5.- La API recibe los datos del usuario de Google y devuelve el token JWT. En la cadena de consulta tendremos la URL de retorno angular. En mi caso si el usuario no está registrado estaba haciendo un paso extra para pedir permiso.

// GET: api/auth/signinexternalcallback
[HttpGet("signinexternalcallback")]
public async Task<IActionResult> SigninExternalCallback(string returnUrl = null, string remoteError = null)
{
    //string identityExternalCookie = Request.Cookies["Identity.External"];//do we have the cookie??

    ExternalLoginInfo info = await _signInMgr.GetExternalLoginInfoAsync();

    if (info == null)  return new RedirectResult($"{returnUrl}?error=externalsigninerror");

    // Sign in the user with this external login provider if the user already has a login.
    Microsoft.AspNetCore.Identity.SignInResult result = 
        await _signInMgr.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);

    if (result.Succeeded)
    {
        CredentialsDTO credentials = _authService.ExternalSignIn(info);
        return new RedirectResult($"{returnUrl}?token={credentials.JWTToken}");
    }

    if (result.IsLockedOut)
    {
        return new RedirectResult($"{returnUrl}?error=lockout");
    }
    else
    {
        // If the user does not have an account, then ask the user to create an account.

        string loginprovider = info.LoginProvider;
        string email = info.Principal.FindFirstValue(ClaimTypes.Email);
        string name = info.Principal.FindFirstValue(ClaimTypes.GivenName);
        string surname = info.Principal.FindFirstValue(ClaimTypes.Surname);

        return new RedirectResult($"{returnUrl}?error=notregistered&provider={loginprovider}" +
            $"&email={email}&name={name}&surname={surname}");
    }
}

API para el paso adicional de registro (para esta llamada, Angular debe realizar la solicitud con ‘WithCredentials’ para recibir la cookie):

[HttpPost("registerexternaluser")]
public async Task<IActionResult> ExternalUserRegistration([FromBody] RegistrationUserDTO registrationUser)
{
    //string identityExternalCookie = Request.Cookies["Identity.External"];//do we have the cookie??

    if (ModelState.IsValid)
    {
        // Get the information about the user from the external login provider
        ExternalLoginInfo info = await _signInMgr.GetExternalLoginInfoAsync();

        if (info == null) return BadRequest("Error registering external user.");

        CredentialsDTO credentials = await _authService.RegisterExternalUser(registrationUser, info);
        return Ok(credentials);
    }

    return BadRequest();
}

Enfoque diferente para aplicaciones de SPA:

Justo cuando terminé de hacerlo funcionar, descubrí que para las aplicaciones de SPA hay una mejor manera de hacerlo (https://developers.google.com/identity/sign-in/web/server-side-flow, Google JWT Authentication con AspNet Core 2.0, https://medium.com/mickeysden/react-and-google-oauth-with-net-core-backend-4faaba25ead0)

Para este enfoque, el flujo sería:

(1) Angular abre la autenticación de Google -> (2) El usuario se autentica -> (3) Google envía googleToken a angular -> (4) Angular lo envía a la API -> (5) API lo valida contra google y devuelve el token JWT -> (6) Token de usos angulares

Para esto necesitamos instalar el ‘angularx-social-login‘npm paquete en Angular y el’Google.Apis.Auth‘Paquete NuGet en el backend de netcore

1. y 4. – Angular abre la autenticación de Google. Usaremos la biblioteca angularx-social-login. Después de que el usuario canta en Angular envía el token de Google a la API.

Sobre el login.module.ts añadimos:

let config = new AuthServiceConfig([
  {
    id: GoogleLoginProvider.PROVIDER_ID,
    provider: new GoogleLoginProvider('Google ClientId here!!')
  }
]);

export function provideConfig() {
  return config;
}

@NgModule({
  declarations: [
...
  ],
  imports: [
...
  ],
  exports: [
...
  ],
  providers: [
    {
      provide: AuthServiceConfig,
      useFactory: provideConfig
    }
  ]
})

En nuestro login.component.ts:

import { AuthService, GoogleLoginProvider } from 'angularx-social-login';
...
  constructor(...,  private socialAuthService: AuthService)
...

  signinWithGoogle() {
    let socialPlatformProvider = GoogleLoginProvider.PROVIDER_ID;
    this.isLoading = true;

    this.socialAuthService.signIn(socialPlatformProvider)
      .then((userData) => {
        //on success
        //this will return user data from google. What you need is a user token which you will send it to the server
        this.authenticationService.googleSignInExternal(userData.idToken)
          .pipe(finalize(() => this.isLoading = false)).subscribe(result => {

            console.log('externallogin: ' + JSON.stringify(result));
            if (!(result instanceof SimpleError) && this.credentialsService.isAuthenticated()) {
              this.router.navigate(['/index']);
            }
        });
      });
  }

En nuestro authentication.service.ts:

  googleSignInExternal(googleTokenId: string): Observable<SimpleError | ICredentials> {

    return this.httpClient.get(APISecurityRoutes.authRoutes.googlesigninexternal(), {
      params: new HttpParams().set('googleTokenId', googleTokenId)
    })
      .pipe(
        map((result: ICredentials | SimpleError) => {
          if (!(result instanceof SimpleError)) {
            this.credentialsService.setCredentials(result, true);
          }
          return result;

        }),
        catchError(() => of(new SimpleError('error_signin')))
      );

  }

5.- API lo valida contra google y devuelve token JWT. Usaremos el paquete NuGet ‘Google.Apis.Auth’. No pondré el código completo para esto, pero asegúrese de que cuando valide el token, agregue la audiencia a la configuración para un inicio de sesión seguro:

 private async Task<GoogleJsonWebSignature.Payload> ValidateGoogleToken(string googleTokenId)
    {
        GoogleJsonWebSignature.ValidationSettings settings = new GoogleJsonWebSignature.ValidationSettings();
        settings.Audience = new List<string>() { "Google ClientId here!!" };
        GoogleJsonWebSignature.Payload payload = await GoogleJsonWebSignature.ValidateAsync(googleTokenId, settings);
        return payload;
    }
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *