You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by ab...@apache.org on 2023/04/23 10:00:59 UTC

[incubator-devlake] branch main updated: Grafana JWT sso (#4990)

This is an automated email from the ASF dual-hosted git repository.

abeizn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 233a62bdd Grafana JWT sso (#4990)
233a62bdd is described below

commit 233a62bdd4e6f640a41177b44576252f8bf24bdb
Author: Klesh Wong <zh...@merico.dev>
AuthorDate: Sun Apr 23 18:00:54 2023 +0800

    Grafana JWT sso (#4990)
    
    * feat(framework): backend support cognito (#4926)
    
    * feat(configui): support cognito (#4928)
    
    * feat(config-ui): adjust the config-ui about cognito (#4933)
    
    * feat(config-ui): hidden signout when token does not exist (#4942)
    
    * fix(api): exclude python register from auth (#4949)
    
    * feat: grafana sso
    
    ---------
    
    Co-authored-by: Warren Chen <yi...@merico.dev>
    Co-authored-by: 青湛 <0x...@gmail.com>
---
 .env.example                            |   8 ++
 backend/go.mod                          |  20 ++--
 backend/go.sum                          |  51 +++++++--
 backend/server/api/api.go               |  71 ++++++++++--
 backend/server/api/login/login.go       |  73 ++++++++++++
 backend/server/api/router.go            |   6 +-
 backend/server/services/auth/cognito.go | 191 ++++++++++++++++++++++++++++++++
 config-ui/src/App.tsx                   |  61 ++++++----
 config-ui/src/layouts/base/base.tsx     |  21 +++-
 config-ui/src/pages/login/api.ts        |  26 +++++
 config-ui/src/pages/login/login.tsx     |  74 +++++++++++++
 config-ui/src/pages/login/styld.ts      |  39 +++++++
 config-ui/src/utils/history.ts          |  22 ++++
 config-ui/src/utils/request.ts          |  15 ++-
 14 files changed, 622 insertions(+), 56 deletions(-)

diff --git a/.env.example b/.env.example
index 9311a30c9..ee4e1bb95 100644
--- a/.env.example
+++ b/.env.example
@@ -43,3 +43,11 @@ ENCODE_KEY=
 # Set if skip verify and connect with out trusted certificate when use https
 ##########################
 IN_SECURE_SKIP_VERIFY=
+
+
+# aws cognito
+AWS_ENABLE_COGNITO=
+AWS_AUTH_REGION=
+AWS_AUTH_USER_POOL_ID=
+AWS_AUTH_USER_POOL_WEB_CLIENT_ID=
+AWS_AUTH_COOKIE_STORAGE_DOMAIN=
diff --git a/backend/go.mod b/backend/go.mod
index 95370a7a4..34a700b92 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -4,7 +4,9 @@ go 1.19
 
 require (
 	github.com/RaveNoX/go-jsonmerge v1.0.0
+	github.com/aws/aws-sdk-go v1.44.242
 	github.com/cockroachdb/errors v1.9.0
+	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/gin-contrib/cors v1.3.1
 	github.com/gin-gonic/gin v1.7.7
 	github.com/go-errors/errors v1.4.2
@@ -35,16 +37,20 @@ require (
 	github.com/x-cray/logrus-prefixed-formatter v0.5.2
 	go.temporal.io/api v1.7.1-0.20220223032354-6e6fe738916a
 	go.temporal.io/sdk v1.14.0
-	golang.org/x/crypto v0.1.0
+	golang.org/x/crypto v0.8.0
 	golang.org/x/exp v0.0.0-20221028150844-83b7d23a625f
 	golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
-	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
+	golang.org/x/sync v0.1.0
 	gorm.io/datatypes v1.0.1
 	gorm.io/driver/mysql v1.3.3
 	gorm.io/driver/postgres v1.4.5
 	gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755
 )
 
+require (
+	github.com/jmespath/go-jmespath v0.4.0 // indirect
+)
+
 require (
 	github.com/KyleBanks/depth v1.2.1 // indirect
 	github.com/Microsoft/go-winio v0.5.0 // indirect
@@ -121,12 +127,12 @@ require (
 	github.com/xanzy/ssh-agent v0.3.0 // indirect
 	go.uber.org/atomic v1.9.0 // indirect
 	golang.org/x/mod v0.8.0
-	golang.org/x/net v0.1.0 // indirect
-	golang.org/x/sys v0.1.0 // indirect
-	golang.org/x/term v0.1.0 // indirect
-	golang.org/x/text v0.4.0 // indirect
+	golang.org/x/net v0.9.0 // indirect
+	golang.org/x/sys v0.7.0 // indirect
+	golang.org/x/term v0.7.0 // indirect
+	golang.org/x/text v0.9.0 // indirect
 	golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
-	golang.org/x/tools v0.2.0 // indirect
+	golang.org/x/tools v0.6.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf // indirect
 	google.golang.org/grpc v1.44.0 // indirect
diff --git a/backend/go.sum b/backend/go.sum
index ea1ad2764..352a9013c 100644
--- a/backend/go.sum
+++ b/backend/go.sum
@@ -75,6 +75,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/aws/aws-sdk-go v1.44.242 h1:bb6Rqd7dxh1gTUoVXLJTNC2c+zNaHpLRlNKk0kGN3fc=
+github.com/aws/aws-sdk-go v1.44.242/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
@@ -126,9 +128,12 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
 github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk=
 github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -229,6 +234,9 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
 github.com/gocarina/gocsv v0.0.0-20220707092902-b9da1f06c77e h1:GMIV+S6grz+vlIaUsP+fedQ6L+FovyMPMY26WO8dwQE=
 github.com/gocarina/gocsv v0.0.0-20220707092902-b9da1f06c77e/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
+github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
@@ -439,6 +447,10 @@ github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/
 github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
 github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -493,6 +505,15 @@ github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdA
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
 github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
+github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
+github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
+github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
+github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0otA=
+github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY=
+github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
+github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
+github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -726,6 +747,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
 go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
@@ -779,9 +801,10 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
-golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
+golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
+golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -819,6 +842,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -870,8 +894,10 @@ golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -896,8 +922,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -970,13 +997,17 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220222200937-f2425489ef4c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -986,8 +1017,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1056,8 +1088,9 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
-golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
-golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/backend/server/api/api.go b/backend/server/api/api.go
index 27f04013e..c0d4c1f08 100644
--- a/backend/server/api/api.go
+++ b/backend/server/api/api.go
@@ -19,6 +19,10 @@ package api
 
 import (
 	"fmt"
+	"github.com/apache/incubator-devlake/server/api/login"
+	"github.com/apache/incubator-devlake/server/api/ping"
+	"github.com/apache/incubator-devlake/server/api/version"
+	"github.com/apache/incubator-devlake/server/services/auth"
 	"net/http"
 	"strconv"
 	"strings"
@@ -54,31 +58,60 @@ Alternatively, you may downgrade back to the previous DevLake version.
 // @host localhost:8080
 // @BasePath /
 func CreateApiService() {
+	// Initialize services
 	services.Init()
+	// Get configuration
 	v := config.GetConfig()
+	// Set gin mode
 	gin.SetMode(v.GetString("MODE"))
+	// Create a gin router
 	router := gin.Default()
+
+	// Check if AWS Cognito is enabled
+	awsCognitoEnabled := v.GetBool("AWS_ENABLE_COGNITO")
+
+	// For both protected and unprotected routes
+	router.GET("/ping", ping.Get)
+	router.GET("/version", version.Get)
+	// Check if remote plugins are enabled
 	remotePluginsEnabled := v.GetBool("ENABLE_REMOTE_PLUGINS")
 	if remotePluginsEnabled {
+		// Add endpoint to register remote plugins
 		router.POST("/plugins/register", remote.RegisterPlugin(router, registerPluginEndpoints))
 	}
-	// Wait for user confirmation if db migration is needed
+
+	if awsCognitoEnabled {
+		// Add login endpoint
+		router.POST("/login", login.Login)
+		// Use AuthenticationMiddleware for protected routes
+		router.Use(auth.AuthenticationMiddleware)
+	}
+
+	// Endpoint to proceed database migration
 	router.GET("/proceed-db-migration", func(ctx *gin.Context) {
+		// Check if migration requires confirmation
 		if !services.MigrationRequireConfirmation() {
+			// Return success response
 			shared.ApiOutputSuccess(ctx, nil, http.StatusOK)
 			return
 		}
+		// Execute database migration
 		err := services.ExecuteMigration()
 		if err != nil {
+			// Return error response
 			shared.ApiOutputError(ctx, errors.Default.Wrap(err, "error executing migration"))
 			return
 		}
+		// Return success response
 		shared.ApiOutputSuccess(ctx, nil, http.StatusOK)
 	})
+
+	// Restrict access if database migration is required
 	router.Use(func(ctx *gin.Context) {
 		if !services.MigrationRequireConfirmation() {
 			return
 		}
+		// Return error response
 		shared.ApiOutputError(
 			ctx,
 			errors.HttpStatus(http.StatusPreconditionRequired).New(DB_MIGRATION_REQUIRED),
@@ -86,34 +119,47 @@ func CreateApiService() {
 		ctx.Abort()
 	})
 
+	// Add swagger handler
 	router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
 
-	//endpoint debug log
+	// Add debug logging for endpoints
 	gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
 		logruslog.Global.Printf("endpoint %v %v %v %v", httpMethod, absolutePath, handlerName, nuHandlers)
 	}
 
-	// CORS CONFIG
+	// Enable CORS
 	router.Use(cors.New(cors.Config{
-		AllowOrigins:     []string{"*"},
-		AllowMethods:     []string{"PUT", "PATCH", "POST", "GET", "OPTIONS"},
-		AllowHeaders:     []string{"Origin", "Content-Type"},
-		ExposeHeaders:    []string{"Content-Length"},
+		// Allow all origins
+		AllowOrigins: []string{"*"},
+		// Allow common methods
+		AllowMethods: []string{"PUT", "PATCH", "POST", "GET", "OPTIONS"},
+		// Allow common headers
+		AllowHeaders: []string{"Origin", "Content-Type"},
+		// Expose these headers
+		ExposeHeaders: []string{"Content-Length"},
+		// Allow credentials
 		AllowCredentials: true,
-		MaxAge:           120 * time.Hour,
+		// Cache for 2 hours
+		MaxAge: 120 * time.Hour,
 	}))
 
+	// Register API endpoints
 	RegisterRouter(router)
-	port := v.GetString("PORT")
-	port = strings.TrimLeft(port, ":")
 	if remotePluginsEnabled {
 		go bootstrapRemotePlugins(v)
 	}
+	// Get port from config
+	port := v.GetString("PORT")
+	// Trim any : from the start
+	port = strings.TrimLeft(port, ":")
+	// Convert to int
 	portNum, err := strconv.Atoi(port)
 	if err != nil {
+		// Panic if PORT is not an int
 		panic(fmt.Errorf("PORT [%s] must be int: %s", port, err.Error()))
 	}
 
+	// Start the server
 	err = router.Run(fmt.Sprintf("0.0.0.0:%d", portNum))
 	if err != nil {
 		panic(err)
@@ -121,12 +167,17 @@ func CreateApiService() {
 }
 
 func bootstrapRemotePlugins(v *viper.Viper) {
+	// Get port from config
 	port := v.GetString("PORT")
+	// Trim any : from the start
 	port = strings.TrimLeft(port, ":")
+	// Convert to int
 	portNum, err := strconv.Atoi(port)
 	if err != nil {
+		// Panic if PORT is not an int
 		panic(fmt.Errorf("PORT [%s] must be int: %s", port, err.Error()))
 	}
+	// Bootstrap remote plugins
 	err = bridge.Bootstrap(v, portNum)
 	if err != nil {
 		logruslog.Global.Error(err, "")
diff --git a/backend/server/api/login/login.go b/backend/server/api/login/login.go
new file mode 100644
index 000000000..7655529c8
--- /dev/null
+++ b/backend/server/api/login/login.go
@@ -0,0 +1,73 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package login
+
+import (
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/server/api/shared"
+	"github.com/apache/incubator-devlake/server/services/auth"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+type LoginRequest struct {
+	Username string `json:"username"`
+	Password string `json:"password"`
+}
+
+type LoginResponse struct {
+	AuthenticationResult AuthenticationResult `json:"AuthenticationResult"`
+	ChallengeName        interface{}          `json:"ChallengeName"`
+	ChallengeParameters  ChallengeParameters  `json:"ChallengeParameters"`
+	Session              interface{}          `json:"Session"`
+}
+type AuthenticationResult struct {
+	AccessToken       string      `json:"AccessToken"`
+	ExpiresIn         int         `json:"ExpiresIn"`
+	IDToken           string      `json:"IdToken"`
+	NewDeviceMetadata interface{} `json:"NewDeviceMetadata"`
+	RefreshToken      string      `json:"RefreshToken"`
+	TokenType         string      `json:"TokenType"`
+}
+type ChallengeParameters struct {
+}
+
+// @Summary post login
+// @Description post login
+// @Tags framework/login
+// @Accept application/json
+// @Param blueprint body LoginRequest true "json"
+// @Success 200  {object} LoginResponse
+// @Failure 400  {object} shared.ApiBody "Bad Request"
+// @Failure 500  {object} shared.ApiBody "Internal Error"
+// @Router /login [post]
+func Login(ctx *gin.Context) {
+	loginReq := &LoginRequest{}
+	err := ctx.ShouldBind(loginReq)
+	if err != nil {
+		shared.ApiOutputError(ctx, errors.BadInput.Wrap(err, shared.BadRequestBody))
+		return
+	}
+	res, err := auth.SignIn(auth.CreateCognitoClient(), loginReq.Username, loginReq.Password)
+	if err != nil {
+		shared.ApiOutputError(ctx, errors.Default.Wrap(err, "error signing in"))
+		return
+	}
+	shared.ApiOutputSuccess(ctx, res, http.StatusOK)
+}
diff --git a/backend/server/api/router.go b/backend/server/api/router.go
index 8408a7af0..26114da9b 100644
--- a/backend/server/api/router.go
+++ b/backend/server/api/router.go
@@ -25,14 +25,12 @@ import (
 	"github.com/apache/incubator-devlake/core/plugin"
 	"github.com/apache/incubator-devlake/server/api/blueprints"
 	"github.com/apache/incubator-devlake/server/api/domainlayer"
-	"github.com/apache/incubator-devlake/server/api/ping"
 	"github.com/apache/incubator-devlake/server/api/pipelines"
 	"github.com/apache/incubator-devlake/server/api/plugininfo"
 	"github.com/apache/incubator-devlake/server/api/project"
 	"github.com/apache/incubator-devlake/server/api/push"
 	"github.com/apache/incubator-devlake/server/api/shared"
 	"github.com/apache/incubator-devlake/server/api/task"
-	"github.com/apache/incubator-devlake/server/api/version"
 	"github.com/apache/incubator-devlake/server/services"
 
 	"github.com/gin-gonic/gin"
@@ -57,8 +55,8 @@ func RegisterRouter(r *gin.Engine) {
 
 	r.GET("/pipelines/:pipelineId/logging.tar.gz", pipelines.DownloadLogs)
 
-	r.GET("/ping", ping.Get)
-	r.GET("/version", version.Get)
+	//r.GET("/ping", ping.Get)
+	//r.GET("/version", version.Get)
 	r.POST("/push/:tableName", push.Post)
 	r.GET("/domainlayer/repos", domainlayer.ReposIndex)
 
diff --git a/backend/server/services/auth/cognito.go b/backend/server/services/auth/cognito.go
new file mode 100644
index 000000000..d2872b13a
--- /dev/null
+++ b/backend/server/services/auth/cognito.go
@@ -0,0 +1,191 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package auth
+
+import (
+	"crypto/rsa"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io"
+	"math/big"
+	"net/http"
+	"strings"
+	"sync"
+
+	"github.com/apache/incubator-devlake/core/config"
+	"github.com/apache/incubator-devlake/impls/logruslog"
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
+	"github.com/dgrijalva/jwt-go"
+	"github.com/gin-gonic/gin"
+)
+
+var (
+	//jwksCache is a cache of the fetched JWKS
+	jwksCache Jwks
+	//jwksCacheMx is a mutex to lock the jwksCache
+	jwksCacheMx sync.Mutex
+	logger      = logruslog.Global.Nested("auth")
+)
+
+func CreateCognitoClient() *cognitoidentityprovider.CognitoIdentityProvider {
+	// Get configuration
+	v := config.GetConfig()
+	// Create an AWS session
+	sess := session.Must(session.NewSession(&aws.Config{
+		Region: aws.String(v.GetString("AWS_AUTH_REGION")),
+	}))
+	// Create a Cognito Identity Provider client
+	return cognitoidentityprovider.New(sess)
+}
+
+func SignIn(cognitoClient *cognitoidentityprovider.CognitoIdentityProvider, username, password string) (*cognitoidentityprovider.InitiateAuthOutput, error) {
+	// Get configuration
+	v := config.GetConfig()
+	// Create the input for InitiateAuth
+	input := &cognitoidentityprovider.InitiateAuthInput{
+		AuthFlow: aws.String("USER_PASSWORD_AUTH"),
+		ClientId: aws.String(v.GetString("AWS_AUTH_USER_POOL_WEB_CLIENT_ID")),
+		AuthParameters: map[string]*string{
+			"USERNAME": aws.String(username),
+			"PASSWORD": aws.String(password),
+		},
+	}
+
+	// Call Cognito to get auth tokens
+	response, err := cognitoClient.InitiateAuth(input)
+	if err != nil {
+		return nil, err
+	}
+	return response, nil
+}
+
+func fetchJWKS(jwksURL string) (jwks Jwks, err error) {
+	// Get the JWKS from the URL
+	resp, err := http.Get(jwksURL)
+	if err != nil {
+		return
+	}
+	defer resp.Body.Close()
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return
+	}
+	// Unmarshal the response into a Jwks struct
+	err = json.Unmarshal(body, &jwks)
+	return
+}
+
+func ensureJWKS(jwksURL string) (jwks Jwks, err error) {
+	// Lock the mutex
+	jwksCacheMx.Lock()
+	defer jwksCacheMx.Unlock()
+
+	// If the cache is empty, fetch the JWKS
+	if len(jwksCache.Keys) == 0 {
+		jwksCache, err = fetchJWKS(jwksURL)
+	}
+	// Return the cached JWKS
+	jwks = jwksCache
+	return
+}
+
+func AuthenticationMiddleware(ctx *gin.Context) {
+	// Get configuration
+	v := config.GetConfig()
+	// Construct the JWKS URL
+	jwksURL := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", v.GetString("AWS_AUTH_REGION"), v.GetString("AWS_AUTH_USER_POOL_ID"))
+	// Get the cached JWKS
+	jwks, err := ensureJWKS(jwksURL)
+	if err != nil {
+		fmt.Printf("Error fetching JWKS: %v\n", err)
+		ctx.Abort()
+		return
+	}
+
+	// Get the Auth header
+	authHeader := ctx.GetHeader("Authorization")
+	if authHeader == "" {
+		http.Error(ctx.Writer, "Authorization header is missing", http.StatusUnauthorized)
+		ctx.Abort()
+		return
+	}
+
+	// Split the header into "Bearer" and the actual token
+	bearerToken := strings.Split(authHeader, " ")
+	if len(bearerToken) != 2 {
+		http.Error(ctx.Writer, "Invalid Authorization header", http.StatusUnauthorized)
+		ctx.Abort()
+		return
+	}
+
+	// Parse the JWT token
+	token, err := jwt.Parse(bearerToken[1], func(token *jwt.Token) (interface{}, error) {
+		// Check the signing method
+		if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
+			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
+		}
+
+		// Get the key ID from the header
+		kid := token.Header["kid"].(string)
+
+		// Look for the key that matches the kid
+		for _, key := range jwks.Keys {
+			if key.Kid == kid {
+				// Construct the RSA public key
+				n := pemHeader(key.N)
+				e := pemHeader(key.E)
+				parsedKey := &rsa.PublicKey{
+					N: new(big.Int).SetBytes(n),
+					E: int(new(big.Int).SetBytes(e).Int64()),
+				}
+				return parsedKey, nil
+			}
+		}
+
+		return nil, fmt.Errorf("Public key not found")
+	})
+
+	// Check if the token is invalid
+	if err != nil || !token.Valid {
+		logger.Error(err, "Invalid token")
+		http.Error(ctx.Writer, "Invalid token", http.StatusUnauthorized)
+		ctx.Abort()
+	}
+}
+
+func pemHeader(encodedKey string) []byte {
+	// Decode the base64 encoded key
+	key, err := base64.RawURLEncoding.DecodeString(encodedKey)
+	if err != nil {
+		return nil
+	}
+	return key
+}
+
+// Jwks represents the JSON web key set
+type Jwks struct {
+	Keys []struct {
+		Kid string `json:"kid"`
+		N   string `json:"n"`
+		E   string `json:"e"`
+	} `json:"keys"`
+}
diff --git a/config-ui/src/App.tsx b/config-ui/src/App.tsx
index ea9b4e969..f655349a5 100644
--- a/config-ui/src/App.tsx
+++ b/config-ui/src/App.tsx
@@ -16,8 +16,9 @@
  *
  */
 
-import { Switch, Route, Redirect } from 'react-router-dom';
-
+import { Switch, Route, Redirect, Router } from 'react-router-dom';
+import { LoginPage } from './pages/login/login';
+import { history } from './utils/history';
 import { BaseLayout } from '@/layouts';
 import { FromEnum } from '@/pages';
 import {
@@ -35,29 +36,47 @@ import {
 
 function App() {
   return (
-    <BaseLayout>
+    <Router history={history}>
       <Switch>
-        <Route path="/" exact component={() => <Redirect to="/projects" />} />
-        <Route exact path="/projects" component={() => <ProjectHomePage />} />
-        <Route exact path="/projects/:pname" component={() => <ProjectDetailPage />} />
-        <Route exact path="/projects/:pname/:bid/connection-add" component={() => <BlueprintConnectioAddPage />} />
-        <Route exact path="/projects/:pname/:bid/:unique" component={() => <BlueprintConnectionDetailPage />} />
+        <Route exact path="/login" component={() => <LoginPage />} />
         <Route
-          exact
-          path="/projects/:pname/create-blueprint"
-          component={() => <BlueprintCreatePage from={FromEnum.project} />}
+          path="/"
+          component={() => (
+            <BaseLayout>
+              <Switch>
+                <Route exact path="/" component={() => <Redirect to="/projects" />} />
+                <Route exact path="/projects" component={() => <ProjectHomePage />} />
+                <Route exact path="/projects/:pname" component={() => <ProjectDetailPage />} />
+                <Route
+                  exact
+                  path="/projects/:pname/:bid/connection-add"
+                  component={() => <BlueprintConnectioAddPage />}
+                />
+                <Route exact path="/projects/:pname/:bid/:unique" component={() => <BlueprintConnectionDetailPage />} />
+                <Route
+                  exact
+                  path="/projects/:pname/create-blueprint"
+                  component={() => <BlueprintCreatePage from={FromEnum.project} />}
+                />
+                <Route exact path="/connections" component={() => <ConnectionHomePage />} />
+                <Route exact path="/connections/:plugin" component={() => <ConnectionListPage />} />
+                <Route exact path="/connections/:plugin/create" component={() => <ConnectionFormPage />} />
+                <Route exact path="/connections/:plugin/:cid" component={() => <ConnectionFormPage />} />
+                <Route exact path="/blueprints" component={() => <BlueprintHomePage />} />
+                <Route
+                  exact
+                  path="/blueprints/create"
+                  component={() => <BlueprintCreatePage from={FromEnum.blueprint} />}
+                />
+                <Route exact path="/blueprints/:id" component={() => <BlueprintDetailPage />} />
+                <Route exact path="/blueprints/:bid/connection-add" component={() => <BlueprintConnectioAddPage />} />
+                <Route exact path="/blueprints/:bid/:unique" component={() => <BlueprintConnectionDetailPage />} />
+              </Switch>
+            </BaseLayout>
+          )}
         />
-        <Route exact path="/connections" component={() => <ConnectionHomePage />} />
-        <Route exact path="/connections/:plugin" component={() => <ConnectionListPage />} />
-        <Route exact path="/connections/:plugin/create" component={() => <ConnectionFormPage />} />
-        <Route exact path="/connections/:plugin/:cid" component={() => <ConnectionFormPage />} />
-        <Route exact path="/blueprints" component={() => <BlueprintHomePage />} />
-        <Route exact path="/blueprints/create" component={() => <BlueprintCreatePage from={FromEnum.blueprint} />} />
-        <Route exact path="/blueprints/:id" component={() => <BlueprintDetailPage />} />
-        <Route exact path="/blueprints/:bid/connection-add" component={() => <BlueprintConnectioAddPage />} />
-        <Route exact path="/blueprints/:bid/:unique" component={() => <BlueprintConnectionDetailPage />} />
       </Switch>
-    </BaseLayout>
+    </Router>
   );
 }
 
diff --git a/config-ui/src/layouts/base/base.tsx b/config-ui/src/layouts/base/base.tsx
index 5716f8c59..5f546a458 100644
--- a/config-ui/src/layouts/base/base.tsx
+++ b/config-ui/src/layouts/base/base.tsx
@@ -17,11 +17,12 @@
  */
 
 import React from 'react';
-import { useLocation, useHistory } from 'react-router-dom';
-import { Menu, MenuItem, Tag, Navbar, Intent, Alignment } from '@blueprintjs/core';
+import { useLocation } from 'react-router-dom';
+import { Menu, MenuItem, Tag, Navbar, Intent, Alignment, Button } from '@blueprintjs/core';
 
 import { Logo, ExternalLink } from '@/components';
 import { useVersion } from '@/store';
+import { history } from '@/utils/history';
 
 import DashboardIcon from '@/images/icons/dashborad.svg';
 import FileIcon from '@/images/icons/file.svg';
@@ -38,9 +39,10 @@ interface Props {
 export const BaseLayout = ({ children }: Props) => {
   const menu = useMenu();
   const { pathname } = useLocation();
-  const history = useHistory();
   const { version } = useVersion();
 
+  const token = window.localStorage.getItem('accessToken');
+
   const handlePushPath = (it: MenuItemType) => {
     if (!it.target) {
       history.push(it.path);
@@ -49,6 +51,11 @@ export const BaseLayout = ({ children }: Props) => {
     }
   };
 
+  const handleSignOut = () => {
+    localStorage.removeItem(`accessToken`);
+    history.push('/login');
+  };
+
   const getGrafanaUrl = () => {
     const suffix = '/d/lCO8w-pVk/homepage?orgId=1';
     const { protocol, hostname } = window.location;
@@ -131,6 +138,14 @@ export const BaseLayout = ({ children }: Props) => {
               <img src={SlackIcon} alt="slack" />
               <span>Slack</span>
             </a>
+            {token && (
+              <>
+                <Navbar.Divider />
+                <Button small intent={Intent.NONE} onClick={handleSignOut}>
+                  Sign Out
+                </Button>
+              </>
+            )}
           </Navbar.Group>
         </S.Header>
         <S.Inner>
diff --git a/config-ui/src/pages/login/api.ts b/config-ui/src/pages/login/api.ts
new file mode 100644
index 000000000..c5a749309
--- /dev/null
+++ b/config-ui/src/pages/login/api.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { request } from '@/utils';
+
+type LoginPayload = {
+  username: string;
+  password: string;
+};
+
+export const login = (payload: LoginPayload) => request(`/login`, { method: 'post', data: payload });
diff --git a/config-ui/src/pages/login/login.tsx b/config-ui/src/pages/login/login.tsx
new file mode 100644
index 000000000..a45cc7f0e
--- /dev/null
+++ b/config-ui/src/pages/login/login.tsx
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { useState } from 'react';
+import { useHistory } from 'react-router-dom';
+import { FormGroup, InputGroup, Button, Intent } from '@blueprintjs/core';
+
+import { operator } from '@/utils';
+
+import * as API from './api';
+import * as S from './styld';
+
+export const LoginPage = () => {
+  const [username, setUsername] = useState('');
+  const [password, setPassword] = useState('');
+
+  const history = useHistory();
+
+  const handleSubmit = async () => {
+    const [success, res] = await operator(() => API.login({ username, password }), {
+      formatReason: (error) => 'Login failed',
+    });
+
+    if (success) {
+      localStorage.setItem('accessToken', res.AuthenticationResult.AccessToken);
+      document.cookie = 'access_token=' + res.AuthenticationResult.AccessToken + '; path=/';
+      history.push('/');
+    }
+
+    setUsername('');
+    setPassword('');
+  };
+
+  return (
+    <S.Wrapper>
+      <S.Inner>
+        <h2>DevLake Login</h2>
+        <FormGroup label="Username">
+          <InputGroup
+            placeholder="Username"
+            value={username}
+            onChange={(e) => setUsername((e.target as HTMLInputElement).value)}
+          />
+        </FormGroup>
+        <FormGroup label="Password">
+          <InputGroup
+            type="password"
+            placeholder="Password"
+            value={password}
+            onChange={(e) => setPassword((e.target as HTMLInputElement).value)}
+          />
+        </FormGroup>
+        <Button intent={Intent.PRIMARY} onClick={handleSubmit}>
+          Login
+        </Button>
+      </S.Inner>
+    </S.Wrapper>
+  );
+};
diff --git a/config-ui/src/pages/login/styld.ts b/config-ui/src/pages/login/styld.ts
new file mode 100644
index 000000000..e12cfadc0
--- /dev/null
+++ b/config-ui/src/pages/login/styld.ts
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import styled from 'styled-components';
+
+export const Wrapper = styled.div`
+  height: 100vh;
+  background-color: #f5f5f5;
+  overflow: hidden;
+`;
+
+export const Inner = styled.div`
+  margin: 200px auto;
+  padding: 16px 24px;
+  width: 400px;
+  background-color: #fff;
+  border-raidus: 4px;
+  box-shadow: 0px 2.4px 4.8px -0.8px rgba(0, 0, 0, 0.1), 0px 1.6px 8px rgba(0, 0, 0, 0.07);
+
+  h2 {
+    margin-bottom: 16px;
+    text-align: center;
+  }
+`;
diff --git a/config-ui/src/utils/history.ts b/config-ui/src/utils/history.ts
new file mode 100644
index 000000000..2b705cfd3
--- /dev/null
+++ b/config-ui/src/utils/history.ts
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// history.js or history.ts
+import { createBrowserHistory } from 'history';
+
+export const history = createBrowserHistory();
diff --git a/config-ui/src/utils/request.ts b/config-ui/src/utils/request.ts
index 450b5421f..2fb8ba59b 100644
--- a/config-ui/src/utils/request.ts
+++ b/config-ui/src/utils/request.ts
@@ -18,8 +18,10 @@
 
 import type { AxiosRequestConfig } from 'axios';
 import axios from 'axios';
+import { history } from '@/utils/history';
 
 import { DEVLAKE_ENDPOINT } from '@/config';
+import { toast } from '@/components/toast';
 
 const instance = axios.create({
   baseURL: DEVLAKE_ENDPOINT,
@@ -35,13 +37,12 @@ export type ReuqestConfig = {
 
 export const request = (path: string, config?: ReuqestConfig) => {
   const { method = 'get', data, timeout, headers, signal } = config || {};
-
   const cancelTokenSource = axios.CancelToken.source();
   const params: any = {
     url: path,
     method,
     timeout,
-    headers,
+    headers: { ...headers, Authorization: `Bearer ${localStorage.getItem('accessToken')}` },
     cancelToken: cancelTokenSource?.token,
   };
 
@@ -51,6 +52,16 @@ export const request = (path: string, config?: ReuqestConfig) => {
     params.data = data;
   }
 
+  instance.interceptors.response.use(
+    (response) => response,
+    (error) => {
+      if (error.response && error.response.status === 401) {
+        toast.error('Please log in first');
+        history.push('/login');
+      }
+    },
+  );
+
   const promise = instance.request(params).then((resp) => resp.data);
 
   if (signal) {