2021年4月18日 星期日

[Notes] 技術管理者論壇 - 商業與技術的平衡


Introduction

  • 大目標切成小目標來執行
  • 少增量,多迭代,多回饋
  • 敏捷以激勵為主,以領導取代管理。
  • 用信心來支持團隊。
  • 我們應該開始稱Legacy code為祖產,而非技術債;因為有它們,才有現在的業務。
  • 除了注重User experience,我們現在也要注重 Developer experience。

商業與技術的平衡

  • 技術債不管理,會越來越糟。
  • 以數據來支援PO或Product Manager的決策。
  • 在RD尚未完整開發完成時,即可從商業端跑敏捷,來驗證我們的新功能是否符合市場需求。
  • 開發者須從"單純的接收和解決問題",進化成"更好的解決問題"。
  • 導入或改變流程需要漸進式,一次到位只會增加失敗機率。
  • VUCA World
    • VUCA comes from Globalization.
    • 從傳統的製造業時代(改變週期慢) -> 網路普及,消費者意識抬頭的C2B(客戶決定市場)。
    • 例如以前鄉下只能從附近的兩家電器行買特定品牌的電視 -> 現在可以網購全台或是全世界的商品。
    • 快速和模糊的市場需求,是敏捷和OKR的興起原因。
    • 商業合作面: 大企業跨界及跨領域,例如Tesla, Amazon。
    • 技術面:新技術推陳出新,但管理層沒有跟上,反而是用舊思維做決策。
  • OKR
    • Why OKR? 因傳統上面一層一層下達指令的速度慢,當基層收到命令時,市場業務已經變化。
    • 用來上下目標對齊。
  • 數位轉型
    • 雙軌職涯規劃(管理/技術)
    • 技術者同時學習部分商業知識(往管理面靠過去),因為管理層學技術難度 > 技術層學商業難度; 畢竟生活中仍會有碰到商業。
    • 上游思維: 不要和上游丟垃圾的人吵架,而是改變思維:"我如何幫助這些能力不足的上游?"。
  • 三大重點:技術,商業,人文。

小組討論

如何說服上層去做RD認為有價值的事情(例如自動化測試/持續整合部屬...etc)

91:

  • 先了解說服對象是誰?
  • 用他聽得懂的語言,並結合他的痛點和期望
    • 和老闆談成本
    • 和PO談減少BUG率及提高修復BUG的效率
  • 用真實數據輔以說明。
  • 不要用RD的術語,因為BU或老闆不懂這些術語帶來的價值,而是要用他們的語言。


心得

  • 我待過極端「以業務為主,技術次要」或「以技術為主,業務次要」的團隊, 前者會導致無法用更好更快的思維來解決問題,而後者就像Gipi大大所講,由技術去找到業務落地的場景,難度是反過來的10倍、100倍。技術很重要,但是追不完。 我的想法是「同時滿足業務和技術創新」,重要的業務先滿足(所以敏捷很重要),新技術做逐步式導入,所以資源的分配就要靠能去上下溝通和整合的“那些出一張的人”(這句話是諷刺阿,如果當過這種角色的人就會知道多辛苦了)。
  • 我在銀行遇過從IT轉到BU單位的User,當然銀行IT一定多少懂業務,所以雙方溝通會比較順暢,因為兩邊都懂對方的困難。 業務端懂一些技術,技術人懂一些業務都是加分; 但是有些人懂了一些對方的皮毛就想要去干涉對方的專業,反而是反效果。
  • 對於「上層不懂新技術,但是卻做技術上的決策」,我倒是想要做個平反,技術上的決策不光只考慮適用性、延展性等技術細節,也要考慮到衝擊、風險和成本; 後者管理層掌握度會比較高,而現實面是管理層要扛責任,所以找架構師及開發團隊一起討論和定決策,可以去解決這個問題。 (經驗還是過程中很重要的參考)

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