2019年11月25日 星期一

[Lets Encrypt] Apply a free SSL cert to IIS

 SSL For Free   Let’s Encrypt   IIS  


Introduction

This article shows how to get a SSL certificate and apply to IIS in 5 minutes XD
See WIKI for more details of non-profit certificate authority, Let’s Encrypt.



Environment


Windows Server 2016
IIS 10



Steps



Create Free SSL Certificate

Go to SSL For Free and

1.  Enter your domain name
2.  Click [Create Free SSL Certificate]




Next click [Manual Verification] -> [Manually Verify Domain],




We will

1.  Download the verify file (which is called “Download File #1” in the below picture)
2.  Put the file to our website for verifying (Notice that the site MUST under port 80 or 443)
3.  We can self-check if the verify-file can be accessed thru the given link
4.  Now we can click on the button [Download SSL Certificate] to get the SSL certificate!


Since I am a .NET guy, I will create a fake site on IIS for step 2 & 3.







How to create a fake site on IIS for verifying

The point is that we have to make the verify-file can be accessed on http://[domain_name]/.well-known/acme-challenge/xxxxxxxxxxx
Create a website on IIS and then under the root directory, create the directories and put the file inside,






Go to IIS Manager -> [MIME Types],





Add a new MIME Type:

File name extension: .
MIME type: text/plain



Or write the MIME type in WebConfig:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <staticContent>
            <mimeMap fileExtension="." mimeType="text/plain" />
        </staticContent>
    </system.webServer>
</configuration>


And then the file can be accessed like this,




Import SSL certificate to IIS

If everything is done, we will get a zip file which contains:





Since IIS supports PFX file, use the following OpenSSL command to generate PFX,

openssl pkcs12 -export -out xxx.pfx -inkey private.key -in certificate.crt -certfile ca_bundle.crt


Now we can import the PFX to IIS and get it works!




Result:






Reference


免費 SSL 申請 (John Wu)







2019年11月13日 星期三

[ASP.NET Core] Identity Server 4 – Client Credential


 ASP.NET Core   Identity Server 4   Client Credential   Machine to Machine  


Introduction



Client Credential is one of the grant types/flows of the OpenID Connect and OAuth 2.0 specifications.

It is used for Machine-to-Machine communications, and no interactive use is involved.

For example, we have two applications, A and B, and they are both Resource Servers.

A can get the Access Token that can be used to access B’s resource(APIs) by Client Credential flow.
In this scenario, A is the Client, B is the Resource Server.





On the other hand, B can get the Access Token that can be used to access A’s resource(APIs) by Client Credential flow.
In this scenario, B is the Client, A is the Resource Server.




We will demo with a simple sample to simulate that granting a Client to access a Resource Server:
·         Client: create a Client in Unit Test
·         Resource Server: the original Backend Server


Environment


Docker 18.05.0-ce
ASP.NET Core 3.0.100
IdentityServer4 3.0.1
IdentityModel 3.10.10



Implement


The source code is on my Github.

Backend Server (Resource Server)

The Resource Server must have the Authentication/Authorization configuration. (Reference: [ASP.NET Core] Identity Server 4 – Secure Web API)
Notice that the Audience is “MyBackendApi2”.


Startup.cs: ConfigureServices

 public void ConfigureServices(IServiceCollection services)
 {
            #region Enable Authentication
            IdentityModelEventSource.ShowPII = true//Add this line
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                string authServerBaseUrl = this.Configuration["Host:AuthServer"];
                bool isRequireHttpsMetadata = (!string.IsNullOrEmpty(authServerBaseUrl) && authServerBaseUrl.StartsWith("https")) ? true : false;
                options.Authority = string.IsNullOrEmpty(authServerBaseUrl) ? "https://localhost:6001" : authServerBaseUrl;
                options.RequireHttpsMetadata = isRequireHttpsMetadata;
                options.Audience = "MyBackendApi2"// API Resource name
                options.TokenValidationParameters.ClockSkew = TimeSpan.Zero// The JWT security token handler allows for 5 min clock skew in default
                options.BackchannelHttpHandler = AuthMetadataUtils.GetHttpHandler();
            });
            #endregion

           
           // … skip
 }


And then we can secure the APIs on the Resource Server (Backend Server).

[HttpGet]
[Authorize]
public ActionResult<stringMyGet()
{
    // ...
}




Auth Server

We have to define a Client-Credential-grant-type Client on Auth Server’s configuration (Class file or JSON file).
Notice that the Client config MUST have allowed scope: MyBackendApi2”, so that the Client can access the Resource Server.


InMemoryInitconfig.cs

 public class InMemoryInitConfig
 {
        // ... Skip GetIdentityResources()
        
        public static IEnumerable<ApiResourceGetApiResources()
        {
            return new ApiResource[]
            {
                  new ApiResource("MyBackendApi2""My Backend API 2")
            };
        }

        public static IEnumerable<ClientGetClients()
        {           
              // Client credentials
                new Client
                {
                    Enabled = true,
                    ClientId = "Resources",
                    ClientName = "Resource Owners",
                    AllowedScopes =
                    {
                        "MyBackendApi2",
                    },
                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    AccessTokenType = AccessTokenType.Jwt,
                    AlwaysSendClientClaims = true,
                    AlwaysIncludeUserClaimsInIdToken = true,
                    IncludeJwtId = true,
                    ClientSecrets = { new Secret("secret".Sha256()) },
                    AccessTokenLifetime = 36000,
                }
            };
        }
    }


Client

I will use an Unit Test to simulate a Client to access the API on Resource Server.
The flow is
1.  Use the Client Credential (Client ID and Secret) to request an Access Token
2.  Create a HttpClient and send request with Authorization Header to access a secured API


We will use IdentityModel as the client library. See my facade class for more details on how to use the IdentityModel.



ClientCredentialTest.cs


 public class TestResourceApi
 {
        private const string SECRETKEY = "secret";
        private const string CLIENTID = "Resources";
        private readonly string remoteServiceBaseUrl = "https://localhost:6001";
        private DiscoveryResponse discoResponse = null;
        private HttpClient httpClient = null;

        public TestResourceApi()
        {
            this.httpClient = new HttpClient();
        }

        [Test]
        public async Task TestSend()
        {
            #region Get access token

            if (this.discoResponse == null)
            {
                this.discoResponse = await this.DiscoverDocumentAsync();
            }

            var tokenResponse = await this.httpClient.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
            {
                Address = this.discoResponse.TokenEndpoint,
                ClientId = CLIENTID,
                ClientSecret = SECRETKEY,
                Scope = "MyBackendApi2"
            });

            if (tokenResponse.IsError)
            {
                Assert.Fail(tokenResponse.Error);
            }
            #endregion

            #region Call protected API

            // Test with correct access token
            this.httpClient.SetBearerToken(tokenResponse.AccessToken);

            var response = await this.httpClient.GetAsync("https://localhost:5001/api/DemoPolicyBased/Everyone/Get");

            if (!response.IsSuccessStatusCode)
            {
                Assert.Fail(response.StatusCode.ToString());
            }
            else
            {
                Assert.True(true);
            }
            #endregion
        }

        private async Task<DiscoveryResponseDiscoverDocumentAsync()
        {
            var isRequireHttps = this.remoteServiceBaseUrl.StartsWith("https");

            this.discoResponse = await this.httpClient.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
            {
                Address = this.remoteServiceBaseUrl,
                Policy =
                    {
                        RequireHttps = isRequireHttps,
                        ValidateIssuerName = true
                    }
            });

            if (this.discoResponse.IsError)
            {
                throw new Exception(this.discoResponse.Error);
            }

            return this.discoResponse;
        }
 }



Unit Test results



If we request the Access Token with wrong scope, we will get the error: “invalid_scope”.



If we use the Access Token with scope “MyBackendApi2”, but try to access the Resource Server with audience: “MyBackendApi1”, we will get the UnAuthorized response.




Furthermore


We can also enable Role|Policy based authorization by adding mapping user claim(s).

See  




Source Code




Reference