Google Oauth Login not working on webapp with C# backend and Angular frontend

Discussão em 'Angular' iniciado por Docas 95, Novembro 8, 2024.

  1. Docas 95

    Docas 95 Guest

    Before asking my question I just want to say that I'm not very experienced with webapps, so pardon me if my mistake is incredibly obvious. But I've been trying to fix it for hours now.

    I have a pretty standard C# webapp. Everything in the backend seems to be working, as I've tested it multiple times. Now, I'm trying to add a frontend UI to it using the Angular framework.

    I have added a navbar component, which should display "Login" if the user is not logged in, and "Logout" if the user is logged in. Currently, when I press "Login" I am sent to the google OAuth process and, when I finish it, the frontend still shows that I am not logged in.

    I have no clue what's going on. Nothing else will work if I can't implement this feature, so I'd really appreciate if someone could help.

    Here's all the relevant code (I think).

    Backend

    Startup.cs:


    namespace g62.backofficeWebApplication
    {
    public class Startup
    {
    readonly bool DoBootstrap = false;
    readonly bool DropTables = false; // not recomended to turn true without DoBootstrap also turning true
    public Startup(IConfiguration configuration)
    {
    Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
    services.AddCors(options =>
    {
    options.AddPolicy("AllowAngularLocalhost", policy =>
    {
    policy.WithOrigins("http://localhost:4200")
    .AllowAnyHeader()
    .AllowAnyMethod()
    .AllowCredentials();
    });
    });

    services.AddControllers();

    services.AddDbContext<EFContext>(options =>
    options.UseMySql(Configuration.GetConnectionString("DefaultConnection"),
    new MySqlServerVersion(new Version(8, 0, 0))
    ).ReplaceService<IValueConverterSelector, StronglyEntityIdValueConverterSelector>());

    services.AddEndpointsApiExplorer();
    services.AddSwaggerGen();

    services.AddAuthentication(options => {
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddGoogle(options =>{
    options.ClientId = Configuration["Authentication:Google:ClientId"];
    options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
    });
    services.AddAuthorization();

    services.AddScoped<Bootstrap>();
    services.AddAuthorization(options => {
    options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin"));
    options.AddPolicy("RequireDoctorRole", policy => policy.RequireRole("Admin"));
    });

    ConfigureMyServices(services);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, Bootstrap bootstrap)
    {
    if (env.IsDevelopment())
    {
    app.UseSwagger();
    app.UseSwaggerUI();
    app.UseDeveloperExceptionPage();
    }
    else
    {
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseCors("AllowAngularLocalhost");

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
    endpoints.MapControllers();
    });

    if (DropTables) {
    bootstrap.DropTables();
    }
    if (DoBootstrap) {
    bootstrap.DoBootstrap();
    }
    }

    public void ConfigureMyServices(IServiceCollection services)
    {
    services.AddTransient<IPatientRepository,PatientRepository>();
    services.AddTransient<PatientService>();
    services.AddTransient<PatientDTOMapper>();

    services.AddTransient<IStaffRepository,StaffRepository>();
    services.AddTransient<StaffService>();
    services.AddTransient<StaffDtoMapper>();

    services.AddTransient<IUserRepository, UserRepository>();
    services.AddTransient<UserService>();
    services.AddTransient<UserDtoMapper>();

    services.AddTransient<IOperationRequestRepository,OperationRequestRepository>();
    services.AddTransient<OperationRequestService>();
    services.AddTransient<OperationRequestDTOMapper>();
    services.AddTransient<OperationRequestFactory>();

    services.AddTransient<IPendingActionRepository,PendingActionRepository>();

    services.AddTransient<ILogsRepository,LogsRepository>();
    services.AddTransient(typeof(Domain.Logs.Logger<>));

    services.AddTransient<IUnitOfWork,UnitOfWork>();

    services.AddTransient<PendingActionsManager>();
    services.AddTransient<NotificationService>();
    services.AddTransient<NotificationSender,GoogleEmailSender>();

    services.AddTransient<WhoAmIService>();
    }
    }
    }```

    LoginController.cs:
    namespace g62.backofficeWebApplication.Controllers {
    public class LoginController : ControllerBase
    {
    private readonly UserService _service;

    public LoginController(UserService service)
    {
    _service = service;
    }

    [HttpGet("login")]
    public IActionResult Login()
    {
    if (User.Identity.IsAuthenticated)
    {
    Redirect("http://localhost:4200/");
    }

    var properties = new AuthenticationProperties{RedirectUri = Url.Action("GoogleResponse", "Login")};
    return Challenge(properties, GoogleDefaults.AuthenticationScheme);
    }


    [HttpGet("logout")]
    public async Task<IActionResult> Logout()
    {
    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    Console.WriteLine("Logged out.");
    return Redirect("http://localhost:4200/");
    }

    [HttpGet("GoogleResponse")]
    public async Task<IActionResult> GoogleResponse()
    {
    var authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);

    if (!authenticateResult.Succeeded)
    return BadRequest("Authentication failed.");

    var userName = authenticateResult.Principal.FindFirst(ClaimTypes.Name)?.Value;
    var email = authenticateResult.Principal.FindFirst(ClaimTypes.Email)?.Value;

    try
    {
    var user = await _service.GetByEmailAsync(email);

    if (user.Active == "False")
    {
    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    return Redirect("http://localhost:4200/");
    }

    var claims = new List<Claim>
    {
    new Claim(ClaimTypes.Name, userName),
    new Claim(ClaimTypes.Email, email),
    new Claim(ClaimTypes.Role, user.Role.ToString())
    };

    var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    var authProperties = new AuthenticationProperties
    {
    IsPersistent = true,
    ExpiresUtc = DateTimeOffset.UtcNow.AddHours(2)
    };

    await HttpContext.SignInAsync(
    CookieAuthenticationDefaults.AuthenticationScheme,
    new ClaimsPrincipal(claimsIdentity),
    authProperties);

    // Redirect the user to the frontend application
    return Redirect("http://localhost:4200/");
    }
    catch (Exception)
    {
    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    return Redirect("http://localhost:4200/");
    }
    }

    [HttpGet("status")]
    public IActionResult GetStatus()
    {
    if (HttpContext.User.Identity.IsAuthenticated)
    {
    return Ok(new
    {
    isAuthenticated = true,
    userName = HttpContext.User.FindFirst(ClaimTypes.Name)?.Value,
    email = HttpContext.User.FindFirst(ClaimTypes.Email)?.Value,
    role = HttpContext.User.FindFirst(ClaimTypes.Role)?.Value
    });
    }
    else
    {
    return Ok(new { isAuthenticated = false });
    }
    }
    }
    }



    Frontend

    auth.service.ts:

    import { Injectable } from '@angular/core';
    import { HttpClient, HttpHeaders } from '@angular/common/http';
    import { Observable, of } from 'rxjs';
    import { tap, catchError } from 'rxjs/operators';

    interface AuthStatus {
    isAuthenticated: boolean;
    userName?: string;
    email?: string;
    role?: string;
    }

    @Injectable({
    providedIn: 'root'
    })
    export class AuthService {
    private apiUrl = 'https://your-backend-url.com'; // Replace with your backend URL
    private userInfo: { userName: string; email: string; role: string } | null = null;

    constructor(private http: HttpClient) {}

    login(): void {
    window.location.href = `${this.apiUrl}/login`;
    }

    logout(): Observable<void> {
    return this.http.get<void>(`${this.apiUrl}/logout`, { withCredentials: true }).pipe(
    tap(() => {
    this.userInfo = null;
    }),
    catchError(this.handleError<void>('logout'))
    );
    }

    getStatus(): Observable<AuthStatus> {
    return this.http.get<AuthStatus>(`${this.apiUrl}/status`, { withCredentials: true }).pipe(
    tap((response) => {
    if (response.isAuthenticated) {
    this.userInfo = {
    userName: response.userName || '',
    email: response.email || '',
    role: response.role || ''
    };
    } else {
    this.userInfo = null;
    }
    }),
    catchError(this.handleError<AuthStatus>('getStatus', { isAuthenticated: false }))
    );
    }

    getUserInfo(): { userName: string; email: string; role: string } | null {
    return this.userInfo;
    }

    private handleError<T>(operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {
    console.error(`${operation} failed: ${error.message}`);
    return of(result as T);
    };
    }
    }


    navbar.component.ts:

    import { Component, OnInit } from '@angular/core';
    import { AuthService } from '../core/services/auth.service';

    @Component({
    selector: 'app-navbar',
    templateUrl: './navbar.component.html',
    styleUrls: ['./navbar.component.css']
    })
    export class NavbarComponent implements OnInit {
    isLoggedIn = false;
    userInfo: { userName: string; email: string; role: string } | null = null;

    constructor(private authService: AuthService) {}

    ngOnInit(): void {
    this.authService.getStatus().subscribe({
    next: (response) => {
    this.isLoggedIn = response.isAuthenticated;
    this.userInfo = response.userInfo || null;
    },
    error: (err) => {
    console.error('Error checking login status', err);
    }
    });
    }

    login(): void {
    this.authService.login();
    }

    logout(): void {
    this.authService.logout().subscribe({
    next: () => {
    this.isLoggedIn = false;
    this.userInfo = null;
    },
    error: (err) => {
    console.error('Error logging out', err);
    }
    });
    }
    }


    navbar.component.html:

    <nav class="navbar">
    <a class="navbar-logo" href="/">Website</a>
    <ul class="navbar-links">
    <li *ngIf="!isLoggedIn"><a href="#" (click)="login()">Login</a></li>
    <li *ngIf="isLoggedIn">
    <a href="#" (click)="logout()">Logout</a>
    <span *ngIf="userInfo">Welcome, {{ userInfo.userName }}</span>
    </li>
    </ul>
    </nav>

    Continue reading...

Compartilhe esta Página