0

my creds in google console

Question:

I am implementing Google OAuth2.0 authentication in my ASP.NET Core MVC application. The process goes through the following steps:

  1. The user logs into my application using a user ID and password.
  2. If authenticated, the user accesses the /sendemails endpoint.
  3. The user is redirected to Google for authentication.
  4. After selecting the Google account, it is supposed to redirect to my callback endpoint https://localhost:7268/sendemails/oauthcallback.

Problem:
Instead of successfully redirecting back to my application after Google authentication, I encounter a 404 error which is a page i did that means Access Denied (user is not authenticated in my system).

OAuth Flow Overview:

  1. User hits https://localhost:7268/sendemails.
  2. Redirects to Google OAuth for account selection.
  3. Fails to redirect to https://localhost:7268/sendemails/oauthcallback and shows a 404 error which is a page i did that means Access Denied (user is not authenticated in my system).

Code Setup

Startup.cs

builder.Services.AddAuthentication(options =>
{
    options.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
    options.LoginPath = "/Account/Login";
    options.AccessDeniedPath = "/Account/AccessDenied";
    options.SlidingExpiration = true;
    options.ExpireTimeSpan = 
TimeSpan.FromMinutes(Convert.ToInt16(builder.Configuration["TimeoutSession:CookieMinutes"]));
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
})
.AddGoogleOpenIdConnect(options =>
{
    options.ClientId = googleCredentials.ClientId;
    options.ClientSecret = googleCredentials.ClientSecret;
    options.CallbackPath = "/sendemails/oauthcallback";
    options.SaveTokens = true;
});

Controller: PrepareEmailController.cs

using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using UMS.BackEnd.DatabaseHelper.Interfaces;
using UMS.Models.ViewModel.EmailViewModels;
using Google.Apis.Auth.AspNetCore3;
using UMS.BackEnd.GoogleHelper;

namespace UMS.Controllers
{
    [Authorize("ViewSendEmailsPolicy")]
    [Route("sendemails")]
    public class PrepareEmailController : Controller
    {
        private readonly IPrepareEmailRepository _repository;
        private readonly GoogleCredentials _googleCredentials;

        public PrepareEmailController(IPrepareEmailRepository repository)
        {
            _repository = repository;
            string credentialsPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "gmailCreds", "credentials.json");
            _googleCredentials = GoogleCredentials.LoadFromJson(credentialsPath);
        }

        public async Task<IActionResult> PrepareEmailForm()
        {
            var accessToken = HttpContext.Session.GetString("AccessToken");

            if (string.IsNullOrEmpty(accessToken))
            {
                return Challenge(GoogleOpenIdConnectDefaults.AuthenticationScheme);
            }

            try
            {
                var employeeId = User.Identity.Name;

                if (employeeId != User.Identity.Name) return Unauthorized();

                var userId = await _repository.GetUserId(employeeId);
                var isUserInstructor = await _repository.IsUserInstructor(employeeId);

                if (isUserInstructor)
                {
                    var studentEmails = await _repository.GetStudentEmailsByInstructor(employeeId);
                    var emailsFrom = await _repository.GetAllEmailFroms(userId);
                    var emailSubjects = await _repository.GetAllEmailSubjects(userId);
                    var emailBodies = await _repository.GetAllEmailBodies(userId);

                    var viewModel = new PrepareEmailFormViewModel()
                    {
                        StudentEmails = studentEmails,
                        EmailFroms = emailsFrom,
                        EmailSubjects = emailSubjects,
                        EmailBodies = emailBodies
                    };

                    return View(viewModel);
                }
                else
                {
                    var emailsFrom = await _repository.GetAllEmailFroms(userId);
                    var emailSubjects = await _repository.GetAllEmailSubjects(userId);
                    var emailBodies = await _repository.GetAllEmailBodies(userId);

                    var viewModel = new PrepareEmailFormViewModel()
                    {
                        EmailFroms = emailsFrom,
                        EmailSubjects = emailSubjects,
                        EmailBodies = emailBodies
                    };

                    return View(viewModel);
                }
            }
            catch (ApplicationException ex)
            {
                TempData["ErrorMessage"] = ex.Message;
                return NotFound();
            }
            catch (Exception ex)
            {
                Console.WriteLine("An unexpected error occurred: " + ex.Message);
                TempData["ErrorMessage"] = "Failed to prepare email data";
                return NotFound();
            }
        }


        [Route("oauthcallback")]
        public async Task<IActionResult> OAuthCallback(string code)
        {
            if (string.IsNullOrEmpty(code))
            {
                TempData["ErrorMessage"] = "Authorization code is missing.";
                return RedirectToAction("PrepareEmailForm");
            }

            try
            {
                var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
                {
                    ClientSecrets = new ClientSecrets
                    {
                        ClientId = _googleCredentials.ClientId,
                        ClientSecret = _googleCredentials.ClientSecret
                    }
                });

                var tokenResponse = await flow.ExchangeCodeForTokenAsync("user", code, "https://localhost:7268/sendemails/oauthcallback", CancellationToken.None);

                HttpContext.Session.SetString("AccessToken", tokenResponse.AccessToken);
                Console.WriteLine("---->Access Token Stored: " + tokenResponse.AccessToken);

                return RedirectToAction("PrepareEmailForm");
            }
            catch (Exception ex)
            {
                TempData["ErrorMessage"] = $"Error during OAuth callback: {ex.Message}";
                return RedirectToAction("PrepareEmailForm");
            }
        }




        [HttpPost("send-email")]
        public async Task<IActionResult> SendEmail(string to, string subject, string body)
        {
            var accessToken = HttpContext.Session.GetString("AccessToken");

            if (string.IsNullOrEmpty(accessToken))
            {
                return Challenge(GoogleOpenIdConnectDefaults.AuthenticationScheme);
            }

            try
            {
                await GmailServiceHelper.SendEmail(to, subject, body, accessToken);

                TempData["SuccessMessage"] = "Email sent successfully!";
                return RedirectToAction("PrepareEmailForm");
            }
            catch (Exception ex)
            {
                TempData["ErrorMessage"] = $"Error sending email: {ex.Message}";
                return RedirectToAction("PrepareEmailForm");
            }
        }



    }
}

Steps Tried:

  1. Verified the redirect URI in Google Cloud Console matches https://localhost:7268/sendemails/oauthcallback.
  2. Ensured the GoogleOpenIdConnectDefaults.AuthenticationScheme is set up correctly in Startup.cs.
  3. Checked the Console output and no logs are being made in OAuthCallback.

Observations:

  • The OAuth flow initiates correctly, and I can select the Google account.
  • Upon selecting the account, the application fails to hit the OAuthCallback endpoint, resulting in a 404 error.

What am I missing?

Is there an issue with the routing or the way I've set up the authentication callback? How can I resolve this 404 error which means my user is no longer authenticated to my system and successfully complete the Google OAuth flow? I am really confused and this workflow of authenticating with google then with my system again is really confusing.

1 Answer 1

0

Actually, there is no need to create the custom callback for the oauth codeflow.

You could directly get the accesstoken inside the MSFT google authentication middleware by using the Microsoft.AspNetCore.Authentication.Google. and you just make sure you have set the Authorize attribute well like this [Authorize(AuthenticationSchemes = "Google")].

It will automatically redirect the user to the google login and get the access token back, then you could use middleware's OnCreatingTicket to get the token and store it inside the claims which you could use it inside the controller later.

More details, you could refer to below codes:

builder.Services.AddAuthentication(options =>
{
    options.DefaultChallengeScheme = "Google"; // Use the scheme name you've configured for Google authentication
}).AddGoogle("Google", googleOptions =>
{
    googleOptions.ClientId = "xxxx";
    googleOptions.ClientSecret = "xxxx";
 
    googleOptions.SaveTokens = true;

    googleOptions.Events = new OAuthEvents
    {
        OnCreatingTicket = context =>
        {

            var accessToken = context.AccessToken;

            //then you could store the access token inside the claims and use it later
            var identity = (ClaimsIdentity)context.Principal.Identity;
            identity.AddClaim(new Claim("access_token", accessToken));
            return Task.CompletedTask;

        }
    };
 });

Inside the controller:

        [Authorize(AuthenticationSchemes = "Google")]
        public async Task<IActionResult> Index()
        {

            var claimsIdentity = User.Identity as ClaimsIdentity;


            if (claimsIdentity != null)
            {
                var accesstoken = claimsIdentity.FindFirst("access_token");

            }
      return View( );
  }

Result:

enter image description here

Not the answer you're looking for? Browse other questions tagged or ask your own question.