Home > other >  Secure API with IdentityServer and C#
Secure API with IdentityServer and C#

Time:10-02

I have my ASP.NET Core (.NET5) project with API controllers. I want to secure my APIs with Identity Server. My goal is to give to some clients access to the APIs based on client_id and client_secret and based on that define what APIs they can call. For that reason, I added in the Startup.cs the following code

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
        .AddIdentityServerAuthentication(options =>
        {
            options.Authority = apiSettings.Authority;
            options.ApiName = apiSettings.ApiName;
            options.ApiSecret = apiSettings.ApiSecret;
        });
}

So, the in each controller I added the [Authorize] attribute. Now, I want to call this APIs from a Console Application or Web Application using HttpClient.

private static async Task<string> GetAccessToken()
{
    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(baseUrl);

        // We want the response to be JSON.
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        // Build up the data to POST.
        List<KeyValuePair<string, string>> postData = new List<KeyValuePair<string, string>>();
        postData.Add(new KeyValuePair<string, string>("scope", "myscope"));
        postData.Add(new KeyValuePair<string, string>("client_id", clientId));
        postData.Add(new KeyValuePair<string, string>("client_secret", clientSecret));

        FormUrlEncodedContent content = new FormUrlEncodedContent(postData);

        // Post to the Server and parse the response.
        HttpResponseMessage response = await client.PostAsync("/api/v1/Test", content);
        string jsonString = await response.Content.ReadAsStringAsync();
        object responseData = JsonConvert.DeserializeObject(jsonString);

        // return the Access Token.
        return ((dynamic)responseData).data;
    }
}

The call always returns 401Unauthorized. What is the correct way to call the APIs with client_id and client_secret? Is scope required?

CodePudding user response:

If you want access control to some operations, I would recommend you to use policies/roles.

Authentication is not supposed to restrict control, it is supposed to check who you working with (and by who I mean concrete entity, with identifier, not his titles, like "Manager", "Administrator", etc, which can be added/removed from entity). Important to mention that Authentication is not preventing user to log in, if he doesn't have some role - it is checked that user correct, and thats it, it shouldn't ban them out of service, Authorization will do it for you.

Authorization and specifically ASP NET Policy/Role usage on the other hand is about checking "permissions"/"titles". Or banning someone entirely.

For permissions/policies which I don't recommend to use, but it can be useful if you want dynamic roles in your service, like specifying them yourself in database:

//you decide which functionality should be restricted
[Authorize(Policy="CanAddStuff")]
public IActionResult AddSomeStuff(){...}

[Authorize(Policy="CanGetStuff")]
public IActionResult GetSomeStuff(){...}

//you decide what concrete role can do.
services.AddAuthorization(options =>
{
    options.AddPolicy("CanAddStuff", policy => policy.RequireRole("Manager"));
    options.AddPolicy("CanGetStuff", policy => policy.RequireRole("Reader"));
});

//somewhere in authentication step of some user you decide it is manager  logged in, and manager in YOUR hierarchy of titles is also a Reader
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Role, "Manager", ClaimValueTypes.String, Issuer));
claims.Add(new Claim(ClaimTypes.Role, "Reader", ClaimValueTypes.String, Issuer));

For titles/roles:

[Authorize(Role="Manager")]
public IActionResult AddSomeStuff(){...}

[Authorize(Role="Reader")]
public IActionResult GetSomeStuff(){...}

which will simply check if your user has Claim (you can add those on authentication step) on it. Which you can freely add in any amount you want, even multiple based on your own hierarchy of Kings/Managers/Readers/Slaves etc.

More here - https://github.com/blowdart/AspNetAuthorizationWorkshop#step-6-multiple-handlers-for-a-requirement

CodePudding user response:

Accessing a secured API requires passing an access token to the header of the API request.

Passing a client id and secret will not provide access. A client id and secret is only for use in non-interactive clients.

There are two recommendations to obtain access to a secure web API method:

Method 1

  1. Call the identity server with either the client id/secret
  2. Client obtains the access token from 1)
  3. Call your API method from the client passing the access token into the request header.

Method 2

  1. Call the identity server with user credentials (user name / password)
  2. Identity server validates user credentials and generate an access token.
  3. Client obtains the access token from 2)
  4. Call your API method from the client passing the access token into the request header.

Method 1 only requires the client id/secret. This is recommended for a non-interactive service that needs to obtain a token from the identity server.

Method 2 requires a custom validation of credentials. Either a third party solution or your own backend secure store. This is a recommended solution for an interactive client application.

  • Related