Ptaptu
Ptaptu

Reputation: 41

Authentication with LinkedIn api and Oauth 2.0 in Angular ASP.NET Core 2.0 Application

I'm new in Angular 5 and I'm using it in ASP.NET Core 2.1 application. I'm trying to implement authentication via external services using their APIs and I get stuck in LinkedIn API. It's quite different API than Google or Facebook, because Linkedin changed his approach and replaced JS API with JSON. I'm using Oauth 2.0 nuget package that provide Linkedin oauth. And I have a problem that with redirecting to the LinkedIn API with login page. I tested the same approach with ASP.NET Core application without Angular and it worked correctly, so the problem is on Angular side or maybe my project is not correctly configured to use Angular.

I added action when LinkedIn button is clicked:

    <button><img class="button-linkedIn" (click)="OnLinkedInLoad()" 
    src="../../assets/images/linkedIn-register2.png"></button>

It's handled in login component:

    @Component({
      moduleId: module.id,
      selector: 'app-login',
      templateUrl: './login.component.html',
      styleUrls: ['./login.component.css']
    })
    export class LoginComponent implements OnInit {

      ngOnInit() {
      }

      constructor(
        private userSrv: UsersService) { }

      OnLinkedInLoad() {
        this.userSrv.loginUserLinkedIn().subscribe()
      }
    }

Login component uses users.service component to send request (GET):

    @Injectable()
    export class UsersService {
      constructor(private httpClient: HttpClient) { }

      addUser(user: User) {
        return this.httpClient.post('/api/Users', user);
      }

      loginUserLinkedIn(): Observable<any> {
        return this.httpClient.get<any>('/api/Users/signInLinkedIn');
      }
    }

And finally in UsersController:

    [Route("api/[controller]")]
    [ApiController]
    public class UsersController : ControllerBase
    {
        public UsersController(LoginTestContext context)
        {
        }

        [HttpGet("signInLinkedIn")]
        public async Task LinkedInSignInAsync()
        {
            await HttpContext.ChallengeAsync("linkedin", new AuthenticationProperties() { RedirectUri = "/" });
        }
    }

And below is my configuration in Startup:

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<LoginTestContext>(options =>
                options.UseNpgsql(Configuration.GetConnectionString("LoginTestContext")));

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
            .AddCookie(
                )
            .AddLinkedIn("linkedin", options =>
            {
                options.ClientId = this.Configuration.GetValue<string>("linkedin:clientid");
                options.ClientSecret = this.Configuration.GetValue<string>("linkedin:clientSecret");
            });

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/dist";
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("Users/Error");                
            }

            //app.UseHsts();
            app.UseSpaStaticFiles();
            app.UseStaticFiles();
            app.UseAuthentication();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Users}/{action=Index}/{id?}");
            });

            app.UseSpa(spa =>
            {
                // To learn more about options for serving an Angular SPA from ASP.NET Core,
                // see https://go.microsoft.com/fwlink/?linkid=864501

                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseAngularCliServer(npmScript: "start");
                }
            });
        }
    }

EDIT:

I noticed an error in console during debugging and after clicking on LinkedIn button there is error log:

Access to XMLHttpRequest at
'https://www.linkedin.com/oauth/v2/authorization?client_id=XXXXXXXXX&scope=r_liteprofile%20r_emailaddress&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A44325%2Fsignin-linkedin&state=CfDJ8NjmD6pBjdpBuQvhUqQ7m6g41igeIumDrha6i0V2JZrOt1u6nweQ9cllyzBMFUj8F2dttqoYX7GYZs9wg-W4O2N8Y9XGPXUwkj5Ojl16sHlYHab93vT3jQEbSXuCQja-Fyths8Rw6YwId0-Ibg8sTeBK-IJSB6_VN16o7h9Nlw24M1Qo3ZRAR8Aq-Yp9DZSdLCVOfzz0yFkvkhy5cJ1OhC0'
(redirected from 'http://localhost:44325/api/Users/signInLinkedIn') from origin 'http://localhost:44325'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I tried some fixes with CORS policy:

1) To add

services.AddCors();

to the ConfigureServices method in Startup and

app.UseCors(builder => builder.AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin());

2) To add header to the HTTPContext response:

this.ControllerContext.HttpContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");

3) To add HttpHeaders when calling loginUserLinkedIn

loginUserLinkedIn(): Observable<any> {
const headers = new HttpHeaders().append('Access-Control-Allow-Origin', ['*']);

return this.httpClient.get<any>('/api/Users/signInLinkedIn', { headers });

But after these fixes the error still occurs.

How should I correctly set CORS policy in ASP.NET Core application that uses Angular?

Upvotes: 4

Views: 2507

Answers (2)

AI Akuma
AI Akuma

Reputation: 499

This code solved it for me in my @Component

onLinkedinLogin() {
      // this.socialAuthService.signInWithLinkedin();
      let linkedInCredentials = {
        clientId: "************",
        redirectUrl: "https://y8pud.codesandbox.io/linkedInLogin",
        scope: "r_liteprofile%20r_emailaddress%20w_member_social" // To read basic user profile data and email
      };
        window.location.href = `https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=*********&redirect_uri=${linkedInCredentials.redirectUrl}&scope=${linkedInCredentials.scope}`;
    }

The code you provided allows users to make requests from an Angular application without getting a CORS error because it performs the request from the browser window itself, rather than from within the Angular application's code.

In the onLinkedinLogin function, the application sets the window.location.href to the LinkedIn authorization URL. This causes the browser to navigate to that URL, initiating a request from the browser directly. This approach bypasses the same-origin policy enforced by the browser, which is the underlying mechanism for CORS.

Since the request is initiated by the browser directly, the browser will handle any necessary CORS headers and negotiations with the server. This allows the request to proceed without encountering a CORS error.

To handle cross-origin requests in that context, proper CORS configuration on the server-side is necessary.

Upvotes: 0

Max
Max

Reputation: 7080

From MDN

Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin.

You try to access the linkedin URL from your localhost URL, this is a Cross-Origin request (from localhost, to linkedin), an you cannot bypass this restriction.

Any setup about CORS must be made on linkedin site ;-)

You want to authenticate the user by c# code by Authenticate Code flow (supported) See this Microsoft Linkedin doc

I don't know ChallengeAsync instruction but, probably you must change the code from

await HttpContext.ChallengeAsync("linkedin", new AuthenticationProperties() { RedirectUri = "/" });

To

Response.Redirect("https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id={your_client_id}&redirect_uri=https%3A%2F%2Fdev.example.com%2Fauth%2Flinkedin%2Fcallback&state=fooobar&scope=r_liteprofile%20r_emailaddress%20w_member_social');

Otherwise I think the challenge is made by the client app (with CORS error).

To know the correct url you can:

  1. Read the Microsoft Linkedin docs, where you can see an example
  2. You can try Fiddler to sniff the url called by the ChallengeAsync and paste to the Response.Redirect command

Upvotes: 3

Related Questions