.NET 5.0 + React + Google Auth

Viewed 314

I am struggling to build a secure React web app with Net 5.0 backend (all in the same project) and would like some advice. All within the same Visual Studio project. Eg:

Project

  • ClientApp (React)
  • Controllers (C# endpoints eg /api/data, api/filtereddata)
  • Program.cs
  • Startup.cs

(Implementation #1) I can make the front-end login using the 'react-google-login' npm package. That works well but it doesn't protect the endpoints in the controllers (/api/data).

<div>
  <GoogleLogin
      clientId={clientId}
      buttonText="Login with Google"
      onSuccess={onSuccess}
      onFailure={onFailure}
      cookiePolicy={'single_host_origin'}
      style={{ marginTop: '100px' }}
      isSignedIn={true}
   />
</div>

I have also discovered I can verify that google token on the server by using something akin to:

const onSuccess = (res) => {
 const tokenBlob = { tokenId: res.tokenId };

 axios.post(`/api/auth/google`, tokenBlob)
      .then(res => {
            })
            .catch(function (error) {
                console.log("axois error", error);
            })
    };
[Route("/api/[controller]")]
public class AuthController : Controller
{
   [AllowAnonymous]
   [HttpPost("google")]
   public IActionResult Google([FromBody] GoogleToken t)
   {
     var payload = GoogleJsonWebSignature.ValidateAsync(t.tokenId, new GoogleJsonWebSignature.ValidationSettings()).Result;
   }
}

But it seems awkward to do this for every API call that the UI makes.

(Implementation #2) I have tried doing this all in the backend (Microsoft.AspNetCore.Authentication.Google), that sort of worked with AddGoogle & AddCookie, that would re-direct to a Google login page when I tried to get data from the backend (via [Authorize]) - but I could not get React to notice that it was logged in/out.

// ConfigureServices
services.AddCors(options =>
{
   options.AddDefaultPolicy(builder => {
      builder.WithOrigins("https://localhost","https://accounts.google.com")
      .AllowAnyHeader().AllowAnyMethod();
   });
});

services.AddControllers().AddNewtonsoftJson(); 

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(o =>
                {
                    o.LoginPath = "/signin";
                    o.LogoutPath = "/signout"; // ??
                    o.ExpireTimeSpan = TimeSpan.FromHours(1);
                })
                .AddGoogle(options =>
                {
                    options.ClientId = "";
                    options.ClientSecret = "";
                    options.SaveTokens = true;
                    options.CallbackPath = "/signin-google";
                });
// Configure
app.UseCors();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();

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

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

app.UseSpa(spa => {
    spa.Options.SourcePath = "ClientApp";

    if (env.IsDevelopment()) {
         spa.UseReactDevelopmentServer(npmScript: "start");
    }
});
// Home controller
public class HomeController : Controller {
    [Route("/signin")]
    public IActionResult SignIn() {
       var authProperties = new AuthenticationProperties { RedirectUri = "/"
            };
       return new ChallengeResult(GoogleDefaults.AuthenticationScheme, authProperties);
    }

     [Authorize]
     [Route("/signout")]
     [HttpPost]
     public async Task<IActionResult> Logout()
     {
        await HttpContext.SignOutAsync();
        return Ok("Logged out");
      }
}

// Api controller
[Authorize]
[Route("api/[controller]")]
public class DataController : Controller
    {
        private readonly ILogger<DataController> _logger;

        public MatchListController(ILogger<DataController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public ResponseViewModel Get([AllowNull] DateTime? d) => new ResponseViewModel(matchDate ?? DateTime.UtcNow)?.Result;
    }

The second implementation will re-direct to Google and require the login but React doesn't know the page is logged in. So how can it get the logged info to display the username etc?

So what is the best practice? Am I close? I feel close!

What I'd love to see would be an example of the WeatherForecast React template in visual studio with a working Google login that uses [Authorize] on the API data controller.

Any suggestions welcome.

Edit: Added some code

0 Answers
Related