顯示具有 ASP.NET Core 標籤的文章。 顯示所有文章
顯示具有 ASP.NET Core 標籤的文章。 顯示所有文章

2024年8月31日 星期六

[ASP.NET Core] HTTP response JSON format

 ASP.NET Core    Newtonsoft.Json    System.Text.Json 


Configuration


The following codes in Startup.cs: ConfigureServices(IServiceCollection services) , show the difference of JSON serialization for HTTP response with Newtonsoft.Json and System.Text.Json.

System.Text.Json

The namespace had been included in runtime in .NET Core 3.1 and later versions.


Newtonsoft.Json

If we still want to use Newtonsoft.Json as the default serialization/deserialization package, install it and write as following. 



2021年7月31日 星期六

[ASP.NET Core] Run the same project at local by different port and environment variable

 ASP.NET Core   Development  

 

Introduction


 

Sometimes we want to run the same project at local as two or more processes that each of them has different port and environment variable.

 

The best way is to containerize your application.

However, if you want a quick solution in order to do some simple tests in this scenario, we can use dotnet CLI and a few setups to achieve it.   

 

Environment


 .NET Core 5.0.301

Visual  Studio 2019 Community

 

 

 

Implement



Here are 2 tips of dotnet CLI to run a project as multiple processes with different port and environment variable.

 

1.Launch profiles


The launchSettings.json file describes how the application launches by specified URL, port, environment variables, etc.

 

We can define multiple launch profile in that file. For example, we have 2 launch profiles in the following launchSettings.json, "Demo.Client1" and "Demo.Client2".

{
  "$schema""http://json.schemastore.org/launchsettings.json",
  "profiles": {
    "Demo.Client1": {
      "commandName""Project",
      "launchBrowser"true,
      "launchUrl""Home",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT""Client1"
      },
      "dotnetRunMessages""true",
      "applicationUrl""https://localhost:5001;http://localhost:5000"
    },
    "Demo.Client2": {
      "commandName""Project",
      "launchBrowser"true,
      "launchUrl""Home",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT""Client2"
      },
      "dotnetRunMessages""true",
      "applicationUrl""https://localhost:5003;http://localhost:5002"
    }
  }
}

 

Now we can switch different launch profile when execute "dotnet run".

 

$ dotnet run --launch-profile Demo.Client1

 

 

Notice that the above command will BUILD THE PROJECT and put the built binaries to "/bin/Debug/". If we modify the code and run another "dotnet run –launch-profile Demo.Client2", it will cause the following error:

 

 

The process cannot access the file '…\src\Demo.Client\bin\Debug\net5.0\Demo.Client.exe' because it is being used by another process.  […\src\Demo.Client\Demo.Client.csproj]

 

 

 

We can specified the "--configuration|-c" argument to build and put the binaries in different path.

 

For example, it is safe to run 2 processes like this:

 

$ dotnet run --configuration client1 --launch-profile Demo.Client1
$ dotnet run --configuration client2 --launch-profile Demo.Client2

 

 

And the binaries will be put at "bin\client1" and "bin\client2".

 

$ ls Demo.Client\bin\
client1/  client2/

 

 

 

 

 

2.Passing program arguments

 

The other tip to use the program arguments.

Since we can set the Kestrel web-server’s options by config file or by WebHostBuilderKestrelExtensions.ConfigureKestrel Method (See Configure options for the ASP.NET Core Kestrel web server), we can take the program arguments and then set the custom configuration for each process.

 

For example, I want to run the same project in 2 processes with different port and different values of environment variable "ASPNETCORE_ENVIRONMENT".

 

$ cd Demo.Client\bin\Debug\net5.0
$ dotnet Demo.Client.dll --urls https://localhost:5001 --env Client1
$ dotnet Demo.Client.dll --urls https://localhost:5003 --env Client2

 

  

The "--urls" is a default argument of "dotnet run" that will set the listening URL of Kestrel webserver. "--env" is the custom argument that we will catch and update the "ASPNETCORE_ENVIRONMENT" value in our application.

public class Program
{
        public static void Main(string[] args)
        {
            if (args is { Length: > 0 })
            {
                #region Set the ASPNETCORE_ENVIRONMENT variable to the process by argument

                const string targetArg = "--env[= ]";
                var reg = new Regex($"{targetArg}.*");
                string env = args.Where(arg => reg.IsMatch(arg)).Take(1) .Select(s => Regex.Replace(stargetArg"")) .FirstOrDefault();
                Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"env);
                #endregion
            }

            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.ConfigureKestrel(serverOptions =>
                    {
                        // Set properties and call methods on options
                    }).UseStartup<Startup>();
                });
}


 

Reference


 

Configure options for the ASP.NET Core Kestrel web server

 

 


2021年4月9日 星期五

[ASP.NET Core] Identity Server 4 – PKCE Authorization Code Flow (Javascript client)

 ASP.NET Core   Identity Server 4   Authorization Code    PKCE   JS  

 



 

 

 

Introduction


 

For how PKCE Authorization Code Flow works, you can have a look on my previous article:  [ASP.NET Core] Identity Server 4 – PKCE Authorization Code Flow.

The flow is as following, we will focus on how to create a JavaScript client that can authenticate user by Identity Server 4 and access the backend’s resource with a given Access Token.




 

 

Notice that I put the JavaScript client at the Backend Server to make the sample code simple (As the following figure).

In the real world, we have to separate the Javascript client (Client) and the Backend Server (Resource server), see reference: [ASP.NET Core] Identity Server 4 – Concepts.



 


Here is the final result’s demo.


 

 


 

Related articles

 

01.      [OpenLDAP] Create an OpenLDAP container

02.      [ASP.NET Core] Identity Server 4 – Concepts

03.      [ASP.NET Core] Identity Server 4 – LDAP authentication

04.      [ASP.NET Core] Identity Server 4 – Secure Web API

05.      [ASP.NET Core] Identity Server 4 – Custom Event Sink

06.      [ASP.NET Core] Identity Server 4 – Refresh Token

07.      [ASP.NET Core] Identity Server 4 – Role based authorization

08.      [ASP.NET Core] Identity Server 4 – Policy based authorization

09.      [ASP.NET Core] Identity Server 4 - Dockerize

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

11.      [ASP.NET Core] Identity Server 4 – Policy based authorization with custom Authorization Handler

12.      [ASP.NET Core] Identity Server 4 – Signing credential

13.      [ASP.NET Core] Identity Server 4 – Authenticate by multiple LDAP

14.      [ASP.NET Core] Identity Server 4 – Cache and refresh Discovery document

15.      [ASP.NET Core] Identity Server 4 – PKCE Authorization Code Flow

16.      [ASP.NET Core] Identity Server 4 – PKCE Authorization Code Flow (Javascript client)

 

 

 

 

Environment


 

Docker 18.05.0-ce

.NET Core SDK 3.1.201

IdentityServer4 3.1.2

oidc-client.js 1.11.5

 

 

 

Implement


 

The source code is on my Github.

 

PKCE Client configuration on Auth Server

 

Go to Idsrv4 project (Auth Server), and open InMemoryInitConfig.cs to set the below configuration on a new client:

 

 

InMemoryInitConfig.cs

 

Notice that there are some key settings:

 

Configuration

Description

Value

AllowedGrantTypes

Grant type

GrantTypes.Code

RequirePkce

Specifies whether a proof key is required for authorization code based token requests (defaults to false).

true

RequireClientSecret

If set to false, no client secret is needed to request tokens at the token endpoint.

false

RedirectUris

The allowed Uri(s) to return Authorization Code or Tokens.

 

PostLogoutRedirectUris

Specifies allowed URIs to redirect to after logout

 

AllowedCorsOrigins

Gets or sets the allowed CORS origins for JavaScript clients.

 

 

public static IEnumerable<Client> GetClients ()
{
     return new [] {
            new Client {
                ClientId = "PkceJS",
                    ClientName = "JavaScript Client",
                    AllowedGrantTypes = GrantTypes.Code,
                    RequirePkce = true,
                    RequireClientSecret = false,
                    RedirectUris = { "https://localhost:5001/OpenId/Login/JS" },
                    PostLogoutRedirectUris = { "https://localhost:5001/OpenId/Login/JS" },
                    AllowedCorsOrigins = { "https://localhost:5001" },
                    AllowedScopes = {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        ApiResources.MyBackendApi2
                    },
                    AllowOfflineAccess = true,
                    AccessTokenLifetime = 3600,
                    RefreshTokenUsage = TokenUsage.OneTimeOnly// Or ReUse
                    RefreshTokenExpiration = TokenExpiration.Sliding,
                    AbsoluteRefreshTokenLifetime = 360000,
                    SlidingRefreshTokenLifetime = 36000,
                    ClientClaimsPrefix = string.Empty,
            }
     }
}


 

We have to implement the AccountController in Identity Server, the code is as same as

[ASP.NET Core] Identity Server 4 – PKCE Authorization Code Flow

 

Or you can see the full code on Github.

 

 

Client side

 

Since we put the JavaScript client on Backend project, which is based on ASP.NET Core.

We have to do some initialization works to enable related JS files.

 

First, install oidc-client.js with NPM.

 

$ cd src/AspNetCore.IdentityServer4.WebApi
$ npm install oidc-client --save

 

 

Copy node_modules/oidc-client/dist/oidc-client.js (or oidc-client.min.js) to wwwroot/js/.

And create the following js files:


File name

Directory

Description

app.js

wwwroot/js

The main javascript to run thru the authentication flow

app-config.js

wwwroot/js

Set the client and identity server’s host URLs, that will be used for redirect URLs after logged in or out.

 

 

The js file structure is as following for reference.

├── node_modules
|  ├── oidc-client
|  |  ├── dist
|  |  |  ├── oidc-client.d.ts
|  |  |  ├── oidc-client.js
|  |  |  ├── oidc-client.min.js
|  |  |  ├── oidc-client.rsa256.slim.js
|  |  |  ├── oidc-client.rsa256.slim.min.js
|  |  |  ├── oidc-client.slim.js
|  |  |  └── oidc-client.slim.min.js
└── wwwroot
   ├── js
   |  ├── app-config.js
   |  ├── app.js
   |  └── oidc-client.js

 

Before we implement the client logic, we have to enable static file serving.

 

Startup.cs: Configure

 

public void Configure(IApplicationBuilder app)
{
        // Use static files
        app.UseStaticFiles();
}

 

 

 

Views/LoginByJs.cshtml

 

 

Let’s create a View as the main page, which will import the js files we had just created.

<h2 id="welcome_msg">
    Welcome!  <label id="uid"></label><button class="btn btn-warning" id="logout">Sign Out</button>
    <button class="btn btn-success" id="api">Test secured API</button>
</h2>
<h2 id="signin_msg">
    Welcome! Please <button class="btn btn-primary" id="login">Sign In</button> first.
</h2>
<table class="table">
    <thead class="thead-dark">
        <tr>
            <th>#</th>
            <th>Key</th>
            <th>Value</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th scope="row">1</th>
            <td>id_token</td>
            <td><label id="id_token"></label></td>
        </tr>
        <tr>
            <th scope="row">2</th>
            <td>access_token</td>
            <td><label id="access_token"></label></td>
        </tr>
        <tr>
            <th scope="row">3</th>
            <td>refresh_token</td>
            <td><label id="refresh_token"></label></td>
        </tr>
        <tr>
            <th scope="row">4</th>
            <td>expires_at</td>
            <td><label id="expires_at"></label></td>
        </tr>
    </tbody>
</table>
<hr />
<h3>Result:</h3>
<pre id="results"></pre>

@section Scripts {
    <script src="~/js/oidc-client.js"></script>
    <script type="module" src="~/js/app-config.js"></script>
    <script type="module" src="~/js/app.js"></script>
}

 

 

The page will be like following, there are three buttons that we will implement their callbacks.

·       Sign In

·       Sign Out

·       Test secured API

 

(Before logged in)


 

 

(Logged in)


 

 

 

app-config.js

 

We will set the client and identity server’s host URLs that will be used for redirect URLs after logged in or out in app.js.


export const AUTH_HOST_URL = "https://localhost:6001";
export const CLIENT_HOST_URL = "https://localhost:5001"; 

 

 

 

app.js

 

Set the OIDC configuration and logging,

import * as constants from './app-config.js';

function log() {
    document.getElementById('results').innerText = '';

    Array.prototype.forEach.call(argumentsfunction (msg) {
        if (msg instanceof Error) {
            msg = "Error: " + msg.message;
        }
        else if (typeof msg !== 'string') {
            msg = JSON.stringify(msgnull2);
        }
        document.getElementById('results').innerHTML += msg + '\r\n';
    });
}

var config = {
    authority: constants.AUTH_HOST_URL,
    client_id: "PkceJS",
    redirect_uri: `${constants.CLIENT_HOST_URL}/OpenId/Login/JS`,
    response_type: "code",
    scope: "openid profile offline_access MyBackendApi2",
    post_logout_redirect_uri: `${constants.CLIENT_HOST_URL}/OpenId/Login/JS`
};
 

 

 

Implement the callbacks of Sign In, Sign Out and Test secured API.


document.getElementById("welcome_msg").hidden = true;
document.getElementById("login").addEventListener("click"loginfalse);
document.getElementById("api").addEventListener("click"apifalse);
document.getElementById("logout").addEventListener("click"logoutfalse);

var mgr = new Oidc.UserManager(config);

mgr.signinRedirectCallback().then(function (user) {
    if (user) {
        document.getElementById("signin_msg").hidden = true;
        document.getElementById("welcome_msg").hidden = false;
        document.getElementById("uid").innerText = user.profile.sub;
        document.getElementById("id_token").innerText = user.id_token;
        document.getElementById("access_token").innerText = user.access_token;
        document.getElementById("refresh_token").innerText = user.refresh_token;
        document.getElementById("expires_at").innerText = moment.unix(user.expires_at).utc();
        log("User logged in"user);
    }
    else {
        log("User not logged in");
    }
});

function login() {
    mgr.signinRedirect();
}

function logout() {
    // Signout
    mgr.signoutRedirect();
}

function api() {
    mgr.getUser().then(function (user) {
        var url = `${constants.CLIENT_HOST_URL}/api/DemoPolicyBased/Admin/Get`// You can change the test API 

        var xhr = new XMLHttpRequest();
        xhr.open("GET"url);
        xhr.onload = function () {
            log(xhr.statusxhr.responseText);
        }

        xhr.setRequestHeader("Authorization""Bearer " + user.access_token);
        xhr.send();
    });
}

 

The full code of app.js is here.

 

 

  

Source Code

Github: KarateJB/AspNetCore.IdentityServer4.Sample

 

 

 

Reference


 Authorization Code Flow with Proof Key for Code Exchange (PKCE)

IdentityModel/oidc-client-js

IdentityServer4: Adding a JavaScript client