4

I'm trying to use ASP.NET middleware to authenticate using Google OAuth. I understand the problem I am getting is due to CORS issues but I cannot seem to resolve them.

My Startup class config is as follows:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
         {
             options.AddPolicy("CorsPolicy",
                builder => builder
                .AllowAnyHeader()
                .AllowAnyMethod()
                .AllowAnyOrigin()
                .AllowCredentials()
                );
      ......
       services.AddGoogle(o =>
            {
                o.ClientId = Configuration["Authentication:Google:ClientId"];
                o.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
                o.AuthorizationEndpoint += "?prompt=consent"; // Hack so we always get a refresh token, it only comes on the first authorization response
                o.AccessType = "offline";
                o.SaveTokens = true;
                o.Events = new OAuthEvents()
                {
                    OnRemoteFailure = ctx =>
                        {
                            ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
                            ctx.HandleResponse();
                            return Task.FromResult(0);
                        }
                };
                o.ClaimActions.MapJsonSubKey("urn:google:image", "image", "url");
                o.ClaimActions.Remove(ClaimTypes.GivenName);
            });
...........
 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();

        //if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseCors("CorsPolicy");

        app.Use(async (context, next) =>
            {
                await next();
                // Serve index file and allow Angular to take over routing if (NotFound)
                if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
                {
                    context.Request.Path = "/index.html";
                    await next();
                }

            });

        app.UseAuthentication();

        app.UseDefaultFiles();
        app.UseStaticFiles();

        app.UseMvc();
    }

In my Auth Controller:

// POST: api/auth/ExternalLogin
    [HttpPost("loginexternal")]
    [AllowAnonymous]
    public async Task<IActionResult> LoginExternal([FromBody]string provider)
    {
        // Clear the existing external cookie to ensure a clean login process
        await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

        // Request a redirect to the external login provider to link a login for the current user
        var redirectUrl = Url.Action(nameof(ExternalLoginCallback));
        var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
        return new ChallengeResult(provider, properties);
    }

My typescript angular code which calls this function:

 loginExternal() {

    const headers = new HttpHeaders({ 'Content-Type': 'application/json', 'Accept': 'application/json' });


    return this.http.post(this.baseUrl + '/auth/loginexternal', '"Google"', { headers: headers })
        .map((res: any) => {
            localStorage.setItem('auth_token', res.auth_token);
            this.loggedIn = true;
            this._authNavStatusSource.next(true);
            return true;
        })
        .catch(this.handleError);
}

And this is the response

405 response

The above response occures after the ChallengeResult is executed in my LoginExternal action.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Your code’s trying to post JSON data cross-origin to `https://accounts.google.com/o/oauth2/auth`, which causes your browser to first send a CORS preflight OPTIONS request to `https://accounts.google.com/o/oauth2/auth` to ask if it’s OK with getting that cross-origin POST. And `https://accounts.google.com/o/oauth2/auth` is responding to tell the browser, *Nope*. So the browser stops right there and never tries your POST request. … Anyway, you can’t “resolve” this—your code is trying to do something that `https://accounts.google.com` explicitly doesn’t want you doing. – sideshowbarker Sep 09 '17 at 17:41
  • ASP.NET middleware is performing that request via the controller though isn't it?. My post method only goes to my controller. –  Sep 09 '17 at 18:28
  • No, your frontend code is what’s trying to perform that POST request. If it were instead the ASP.NET middleware, it would just work—because ASP.NET middleware doesn’t enforce the same-origin policy and enforce cross-origin restrictions. Browsers do. That’s why it’s your browser that’s showing you that CORS error message. – sideshowbarker Sep 09 '17 at 18:34
  • Then why does this code work when using the default ASP.NET MVC template and views where the auth code was pulled from? Sorry I'm trying to find how to fix it... When stepping through the debugger the post method hits my controller the error only occurs after the middleware ChellengeResult method executes. –  Sep 09 '17 at 18:46
  • 1
    Any update on this? I have got the same issue. – Nuwan Dec 18 '18 at 04:58

1 Answers1

1

Try to use this.document.location.href or even window.location.href to redirect to your google authentication page instead of making http request to your .net core controller action.

@Injectable()
export class LoginService {
    //...

    constructor(@Inject(DOCUMENT) private document: Document,...)

    login() {
        this.document.location.href 'https://www.mywebsite.com/account/signInWithGoogle';
    }
}

Here’s how that looks like in a controller action:

public class AccountController : Controller
{
    private readonly SignInManager<IdentityUser> _signInManager;
    private readonly UserManager<IdentityUser> _userManager;
    public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager)
    {
        _signInManager = signInManager;
        _userManager = userManager;
    }

    public IActionResult SignInWithGoogle()
    {
        var authenticationProperties = _signInManager.ConfigureExternalAuthenticationProperties("Google", Url.Action(nameof(HandleExternalLogin)));
        return Challenge(authenticationProperties, "Google");
    }

    ...

Guide: https://www.blinkingcaret.com/2018/10/10/sign-in-with-an-external-login-provider-in-an-angular-application-served-by-asp-net-core/