OAuth security for website with API and only external providers

84 views Asked by At

I have a .Net core 3.2 site with a RESTful API on one server, and a client website on another server. Users authenticate to the client app via only external providers such as Facebook, Google, or Microsoft. We also have an Identity Server 4.0 that we will be using, but will act just like another external provider.

The issue is that once the user is authenticated on the client, and their granted roles/claims have been determined, how do we request a particular resource from the API? The client web app knows about the user and knows the user is who they say they are, and the client app knows what they can do. How do we relay that information securely to the API?

I was considering client_credentials between the API and the web site, but it seems that is for situations where there is no user, like services or daemons.

I don't want the API to know or care about the users, just that they are authenticated and what their claims are.

2

There are 2 answers

2
Vlad DX On

To implement authentication in a single-page application, you need to use Authorization Code with PKCE OAuth2 flow. It lets you not store any secrets in your SPA.

Please don't use Implicit flow as it's deprecated because of security reasons.

When you send your token from a client to a properly configured .NET Core API, you should be able to read the User property of the controller for the identity information.

If you configure the API properly, a request will reach a controller only in case if an access token is valid.

2
Michael Horn On

The answer I was looking for was JWT Tokens: On the client, before it sends the bearer token:

 protected override async Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            var accessToken = await GetAccessTokenAsync();

            if (!string.IsNullOrWhiteSpace(accessToken))
            {
                request.SetBearerToken(accessToken);
            }

            return await base.SendAsync(request, cancellationToken);
        }

  public async Task<string> GetAccessTokenAsync()
        {

            var longKey = "FA485BA5-76C3-4FF5-8A33-E3693CA97002";

            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(longKey));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var claims = new List<Claim> {
                new Claim("sub",  _httpContextAccessor.HttpContext.User.GetUserId())
            };

            claims.AddRange(_httpContextAccessor.HttpContext.User.Claims);
            
            var token =new JwtSecurityToken(
                issuer: "https://localhost:44389",
                audience: "https://localhost:44366",
                claims: claims.ToArray(),
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: credentials
            );

            return new JwtSecurityTokenHandler().WriteToken(token);
        }

And on the API server

 var longKey = "FA485BA5-76C3-4FF5-8A33-E3693CA97002";

            services.AddAuthentication(x=> {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
                .AddJwtBearer(options =>
                {
                    options.SaveToken = true;
                    options.RequireHttpsMetadata = false;
                    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                    {
                        ValidateIssuer = false,
                        ValidateAudience = false,
                        //ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        //ValidIssuer = "https://localhost:44366",
                        //ValidAudience = "https://localhost:44366",
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(longKey)),
                        //ClockSkew = TimeSpan.Zero
                    };

                    
                });