bcgov / nr-forests-access-management

Authorization solution for BC natural resource sector
Apache License 2.0
8 stars 2 forks source link

Frontend login authentication implementation with JWT token. #260

Closed ianliuwk1019 closed 1 year ago

ianliuwk1019 commented 1 year ago

As an administrator for FAM I want to have a login So That* I know I can securely log into the FAM system with my credential (IDIR/BCEID) *

Additional Context

Acceptance Criteria

Definition of Done

ianliuwk1019 commented 1 year ago

A Sample Cognito app for reference (not Vue specifically): https://github.com/bcgov/cognito-example-apps/tree/main/examples.

The sample react-amplify code does work for testing on our Cognito integration. The working configuration for the 'aws-exports.js' is this (based on current Cognito values, may change): ` const config = { aws_cognito_region: 'ca-central-1', aws_user_pools_id: 'ca-central-1_5BOn4rGL8', aws_user_pools_web_client_id: '26tltjjfe7ktm4bte7av998d78', // This is App Client Id aws_mandatory_sign_in: 'enable', oauth: { domain: 'dev-fam-user-pool-domain.auth.ca-central-1.amazoncognito.com', scope: ['openid'], redirectSignIn: 'http://localhost:3000/cognito/callback', redirectSignOut: 'http://localhost:3000/cognito/logout', responseType: 'code', }, federationTarget: 'COGNITO_USER_POOLS', };

export default config; `

ianliuwk1019 commented 1 year ago

The sample react-amplify code (after fixing Cognito domain configuration value) does show (on dev console) there is a request to get token and return this value as response: {"id_token":"eyJraWQiOiJBRUI1T0lTaDlOS2NXXC9mWGRPWndrbFRrS0pxZVgrMmp1cFJWMXRoS093ND0iLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoicVpPQmJzcXZyenBMMU5qM29sMDQyUSIsInN1YiI6ImE4MTE2MWVlLWEzMTMtNDFkNC04OGU0LTQ4NjU5NDg5ZjQzYSIsImNvZ25pdG86Z3JvdXBzIjpbImNhLWNlbnRyYWwtMV81Qk9uNHJHTDhfSURJUiJdLCJjdXN0b206aWRwX25hbWUiOiJpZGlyIiwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLmNhLWNlbnRyYWwtMS5hbWF6b25hd3MuY29tXC9jYS1jZW50cmFsLTFfNUJPbjRyR0w4IiwiY29nbml0bzp1c2VybmFtZSI6ImlkaXJfZTcyYTEyYzkxNmE0NGE5NTgxY2YzOWU1ZGNkZmZhZTdAaWRpciIsIm5vbmNlIjoiNGwyRUpTdTZGWGJ1NEtvZUhfaldRX0JDejlqM3U4LW1DMlVGeFhuYmJJVURXb0U1cU9FZXBLM2RvVUR2aFh5SDd1LW5Xd0Z6bG9vWWVhOGx5dXVrNVAwSzg2YjNGNkN6N0RndXl4ZGtpN3p5NE90R2V6SzgxMUxjRWc3Q0Zaa0doLU5vYUJYWFRxbEl6S29pYk9sWHd0VlJxNjdLT3E1WS1IWDU1ZS1RZkI4IiwiY3VzdG9tOmlkcF91c2VyX2lkIjoiRTcyQTEyQzkxNkE0NEE5NTgxQ0YzOUU1RENERkZBRTciLCJvcmlnaW5fanRpIjoiZTc5YzY1YzItMjViMi00OGUzLWExOWUtODM3YTk5YTkyZmM4IiwiYXVkIjoiMjZ0bHRqamZlN2t0bTRidGU3YXY5OThkNzgiLCJjdXN0b206aWRwX3VzZXJuYW1lIjoiSUFOTElVIiwiaWRlbnRpdGllcyI6W3sidXNlcklkIjoiZTcyYTEyYzkxNmE0NGE5NTgxY2YzOWU1ZGNkZmZhZTdAaWRpciIsInByb3ZpZGVyTmFtZSI6IklESVIiLCJwcm92aWRlclR5cGUiOiJPSURDIiwiaXNzdWVyIjpudWxsLCJwcmltYXJ5IjoidHJ1ZSIsImRhdGVDcmVhdGVkIjoiMTY2NzU5NjY5NjA3MSJ9XSwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE2Njc4Njg1NTgsImV4cCI6MTY2Nzg3MjE1OCwiaWF0IjoxNjY3ODY4NTU4LCJqdGkiOiIzMDA3YWYzNi0wMjNkLTQ0NGEtYWZmNC1jMWY4MzlkZmEzNjAifQ.a5VwtihtxRB4GdWydgmRmrKAAm7lc-TX_j7RonpxsLVMzAFRbPGH7wWKthMV4sWI-xY0qkyISetPnXJc8Y6aNno2CJ8hWflED9zK0G5Fsy1Bi8Ehzs62gkatTJgETgeoIB_HceLPPAlG2O7TuTrdJBfge684yNpE2bvYYyxXjazi6c_wm6V0N9U1d1rBx1O5ZxSEbjgA0MhUHcewTIW2ItvEGvjcCQBcZGOr3z8S36mj3M2PKbkpUmjZ60-Sd3au0K0gDG0bAk0TGRn3N17tUCU8HBp9bGlVKaHlUYKR6oD9HBj3eolpARb-vMxFeKDHGx22hp8Pvo4MrmTwDjg_HQ","access_token":"eyJraWQiOiJGTk44Qkh0bXNnb0JXdW9IOFhZS3p2K25QZVB5VnIrb3RcL0d6RFNLaVdPRT0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhODExNjFlZS1hMzEzLTQxZDQtODhlNC00ODY1OTQ4OWY0M2EiLCJjb2duaXRvOmdyb3VwcyI6WyJjYS1jZW50cmFsLTFfNUJPbjRyR0w4X0lESVIiXSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLmNhLWNlbnRyYWwtMS5hbWF6b25hd3MuY29tXC9jYS1jZW50cmFsLTFfNUJPbjRyR0w4IiwidmVyc2lvbiI6MiwiY2xpZW50X2lkIjoiMjZ0bHRqamZlN2t0bTRidGU3YXY5OThkNzgiLCJvcmlnaW5fanRpIjoiZTc5YzY1YzItMjViMi00OGUzLWExOWUtODM3YTk5YTkyZmM4IiwidG9rZW5fdXNlIjoiYWNjZXNzIiwic2NvcGUiOiJvcGVuaWQiLCJhdXRoX3RpbWUiOjE2Njc4Njg1NTgsImV4cCI6MTY2Nzg3MjE1OCwiaWF0IjoxNjY3ODY4NTU4LCJqdGkiOiI3MTU5ODI0NS1iZGJhLTRkZDctYWM2OC1kOTlhMDA4MGVhYTYiLCJ1c2VybmFtZSI6ImlkaXJfZTcyYTEyYzkxNmE0NGE5NTgxY2YzOWU1ZGNkZmZhZTdAaWRpciJ9.oGJIyvcO7BdHr7NH4JohGbH1P4xScdJWMxf_fWJeCXFdgFRV0tsUvyJW5Dt3ky3ZcpYrMy8Ub2uG86Mif3lmIlS9W-inWIqbfistpLsP65Qm6zCQ31kuMqEkw8KnUGBZA4yKrjX8V9XyIImE3ylQjAALOL1MncMt7q7P6iFT9nhxMW3sAgFCJDTZ7h_DzC4ALJThNUl1cnOKb-NbOj85vFLhSyMnUlFTR19zu6ZnOwtyY4xo4No02_dWJJsE91fskRhwMz3XdTEa47vteg0PAc3_g5oYLsf_BAn_vlR6uKQlhcpenpIPRtsaQYeWEff97gpP3tdKWB3Gr8vPO3ZT-A","refresh_token":"eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.3ia_IOsKKQRf_iPxWsuoNBXbep28DmmbnUj-Hl1-5s92P-Sn5FlPR1yPQrCMA-OR30SZQpPvUfPUWWlE0MHxg-PQkvL9EfpwO5O7Vv_o0XDSCnfUcf4g2FDMfN-YPxYySZEmGNKAL8xHqEms7aeK4MG_SAvZUAcYqLj1nGGxZVXGx0W6bJLNS5W9IMnBfMdW038Kr-neOfyaErtFpt8GLlviXSTC46xe7OJSzPPm26ALAR4FwEJ07rPWfdfc97Y2oeCx05OE41a8zviiPVqUJlJL7hQQPeEvwDD0QlltpbYlFxeADjFZxv_IbA5kmqWdlk7OqAqXDuE6Mtvseh-ADw.Lc-uL4KqaS2r8XRB.UbDftd6O3FWVS_a5DxfmUmLsgQ4GVQtWONP42PTsVAmOT1vR-vhHMqBuf2pXqj6xAAxBBZ6WR-ICS1TWvkjGVRqGeWfqQ0vWzb9kkQZ5NItLqCwtl2Fk5LIFeIoJePHAKtrGLfANJDUFtdjmPQ5xTop-L8NxqcJYEcXqD0sKxOjlixh2ylv8ywqFRB8WWTYD6zA6ZTzsOZAU9DhKKXMxaWu17gaDWCmHJ2zy6Uu2HL47SHbpnitjLvM-Mknb9ec_g5lRvgFgF_Peh9YQDD4CZ-K9NuciYotIvo5BDzoz6q9KW8t5c-sV64mjxPAPs0ob8bnOLHPV7fVxVE425o4FtNE8xuLOiB_b5ew3KQXSFPrdHWwYBu3ihvW0V40k6_pF91umiQ7GFhDHJchU8dvOb9fShf7NTrGtdHQ3syKwv4CEF18PH_xTGv9eilm05rAS4RUMHwIhPcDoQCHXJzMh14mkJoRcgaDXgJQCmT9Q2cp8IGMqti9rGtHjEV811M3zct61Xx5RiyKL-dD7tixOzTqVOqPGmPMW9Mf7yc82k08JFBxOpS6h6W-2zULdJdl5Vs7KWoOKN1Neurm9_10pkbRweZywA69ibe9epgJyTWi73Qb3P8xYGLvQv80Vop62qZkFTRYNWMh8-RHZhLgfaB4-WmHAfk4bUKesrq03UT_dbDQSGCyPpvOkbabbzAb080QQpK-gZQSimpKMtWLiM_ZgSmL8qJbmfsojiOIuH_JLAdMo_fKk7NK5cPZ6gjbnm6qN14EpU4xAgnAPsdR-qM1zTDagJSIrvrJ494kLv7cu0YQzPXdxJ2xH9c9rtZVZeQjz7ADOXKpPMQ_7v_qc648BehdCL0miuS1yfkc9GeHshBMCGL-UTuIXfhD4Ze-bArqPSKumh6bn6hgu-ElsG1hzrmqTVKfcMuBiOSMuF5dPUYIGgaa0jDryPbYQ9wg-1YJ01zGlDfVJBmL50QZ_KojqSFOECUfRBSZhJD4j8eCJ6-pN16OBrQfNBFMnd3PmzzMr3XqK7iks-a0QgTY1WJ2V82eoSItF4ryX3locLUpb_TPyB08869XMIjqrwmXmV-Dwux1pGaPQkjVXT1upBhbVvWkV4cImClX3yH8zFpJvHhIXLMxFY6ppH9XLP8TLaq8mK-D2MDbxpBEUeRx3-_IiSpeElk5N0evuWqcbzpuEgzp0utZjJ-_1VMRJz4A2zvJTFhtYpsyIKZU6KTpKx6T4FSfJaH6DtbeI.TRfSo-alV0QXXKUrbKpFeg","expires_in":3600,"token_type":"Bearer"}

The react code does not look like doing specific request (no code seen) to Cognito, so I assume the lib (Amplify) must be internally doing the request. Will need to find the Amplify lib documentation to see how to retrieve it for our Vue app to set into our state/localstorage.

ianliuwk1019 commented 1 year ago

The 'react-app-amplify' provided a functional example connecting to our Cognito setup on AWS. Although there is a similar library from Aws-Amplify 'aws-amplify-vue', the setup does not work for our current Vue 3 (Typescript) project. What I encountered:

After some research, this 'aws-amplify-vue' seems to be outdated package and only for javascript (not typescript) project. And found Amplify has provided a newer lib to deal with Vue3: https://ui.docs.amplify.aws/vue/getting-started/installation#vite However its main Aws-Amplify setup instruction for Vite does not work (https://docs.amplify.aws/lib/project-setup/create-application/q/platform/js/#vue-vite-config). This is what I encountered:

Some internal issue from Amplify itself with aws-sdk. There were some discussion here (https://github.com/aws-amplify/amplify-js/issues/9639) and the thread is too long and don't really know what the solution is.

After some googling, this seems to be the answer that gets our project build working: https://dev.to/ilumin/vite-build-failed-on-project-with-aws-sdk-14dk (Look at Jun 23 comment/solution from Wesley Cheek)

However, without further experimenting, I don't know if that would really solve the problem yet. I Will need to dig into further in the documentation to see how we can use it to redirect for authentication and how to get the token, probably not the same as the example react app.

ianliuwk1019 commented 1 year ago

The integration of frontend with Cognito is successful. Current in my local branch (in review) it uses aws server dev environment settings from Cognito to do the redirection and authentication. For our case/purpose, after some understanding and experiments, we do not need to use aws-amplify/ur-vue (Amplify's Vue component) as we are using oidc federated flow. Amplify document does not make it clear example on this approach but these two are the most relevant references on the documentation for us: https://docs.amplify.aws/lib/auth/social/q/platform/js/ https://docs.amplify.aws/lib/auth/advanced/q/platform/js/#identity-pool-federation

ianliuwk1019 commented 1 year ago

Before having frontend vanity URL for environments ready, Conrad has helped temporarily coded in Terraform with frontend URL parameters to be used for Cognito callback url(2 urls). This is in PR: https://github.com/bcgov/nr-forests-access-management/pull/293.

For the current PR#291, it is failing in GH action before it can run frontend tests in pipeline with "internal error" that's from Vitest library: Internal error: Error constructor is not present on the given global object.

It is unclear how the library got failed. After some looking into it, it seems to be some configuration issue/conflicts from the Vite.config.ts (and it is internally, not our code)... and from the googling I find, there seems to be no solution yet. I feel, vue3/Vite ecosystem aren't that yet stable enough and this particular problem might either prompt that we temporarily disable test in GH action for quick turn around or need to find alternative on test framework (and it may take extra long time). Need to discuss... @basilv

basilv commented 1 year ago

Hey @ianliuwk1019 have you tried building & running tests locally, does that work?

ianliuwk1019 commented 1 year ago

The 'global' object issue is resolved with using alternative fix from index.html include script tag that defines a 'global' variable for use. Comment is added in code to explain this is current alternative solution to does not conflict with Vitest setting in Vite configuration.

ianliuwk1019 commented 1 year ago

All PRs merged to main and uses Terraform's output values with generated env.json to deploy in AWS dev environment. Local build and tests passed. We are almost there but not ready yet for deployment environment with frontend 'production' build. See a problem when accessing frontend url. It does not work yet, but it is good, at least we now see the issue early.

When browser downloads the production build (that creates minified javascript file) it has problem calling window.localstorage.getItem('some_key') because it is null or not defined yet (hasn't been set before it gets called). I will discuss with team in the morning and see what approach can take to resolve this. My current thinking is leaning toward understanding how Vue 3 get/use environment variable or how it initialize global setting.

ianliuwk1019 commented 1 year ago

Issue #301 has now resolved and unblocked/completed this ticket. In summary -

In dev's terragrunt.hcl:
    local_frontend_redirect_path = "http://localhost:5173"

In variable_provided.tf:
    variable "local_frontend_redirect_path" {
       description = "Path to local FAM front-end (for redirect URI), only for dev"
       type = string
       default = ""
   }

   In oidc_clients.tf:
  callback_urls = [
    "${var.front_end_redirect_path}/authCallback",
    "${var.local_frontend_redirect_path}" != "" ? "${var.local_frontend_redirect_path}/authCallback" : ""
  ]
  logout_urls = [
    "${var.front_end_redirect_path}/authLogout",
    "${var.local_frontend_redirect_path}" != "" ? "${var.local_frontend_redirect_path}/authLogout" : ""
  ]

   In Vue's 'aws-exports.js', it will be substituted with param:
      redirectSignIn: `${env.front_end_redirect_base_url.value}/authCallback`,
      redirectSignOut: `${env.front_end_redirect_base_url.value}/authLogout`,
  1. It was found that the cause was because the env.json (contains environment specific params generated from Terraform) served from /public folder did not get downloaded to the browser in sever environment but works in local environment (still unknow to me for the reason). To resolve this issue, instead of using javascript 'import' at "env.js" script to import the json file, I use "fetch" library to request for the "env.json" at server causing it to be download to the browser. This is tested to be working for production build and deployed to aws.
    fetch('/env.json')
    .then(res => res.json())
    .then(data => {
    window.localStorage.setItem('env_data', JSON.stringify(data))
    })
    1. Although it is working now on aws for frontend deployment and the auth works with Amplify using env.json values, we encountered a 403 issue after Cognito attempts to redirect to the "/authCallback".
      <Error>
      <Code>AccessDenied</Code>
      <Message>Access Denied</Message>
      <RequestId>HG8ARWYNFJ0YN4SY</RequestId>
      <HostId>iEkP/q6okWK2BirKjTfZuMskfUtTgnI8Ki/OyDtYrJ96GWz1l5x9MoJ7/O8dXWUEq4aqU5JNnjE=</HostId>
      </Error>

      With @basilv 's help on quick investigation on aws Cloudfront, it turns out that we can use Cloudfront's "Custom Error Response" that would acts as proxy server path locating strategy to pass through when 403 is encountered. The reason for that 403 error from Cloudfront was because the callback has a "/path", in this case "/authCallback" and S3 does not have this 'folder or file' because Cloudfront only recognizes/serves from the static root and after Cognito's redirect, the root index.html hasn't been loaded yet so the SPA (Vue) has not kicked in to recognize the routing (/path) yet. The thought is convincing and I feel it is correct, however, from the http point of view "404" should have been the right error and not 403 if the path/resource is not found. And, actually, I did see that 404 while testing locally with http-server when serving up production build with this command in 'dist folder: npx http-server -p 5173 but I wasn't carefully enough to link 403 issue (on Cloudfront) might be the 404 proxy issue I see locally. I didn't quite get why 403 error was throw from Cloudfront so search on internet, there is a reference I see explains it might be default AWS Cloudfront behavior when Cloudfront did not have error response page setup. @basilv may have good documentation from AWS where he found the trick to do the SPA when 403 shows up but I don't know where to find it. This is how we fix it:

      In cloudfront.tf:
      custom_error_response {
      error_code = 403
      response_code = 200
      response_page_path = "/"
      }

      Also thanks to Basil to setup gated deployment to 'dev' with his GitHub admin permission so I could try few times to finish this ticket!!

@ArogeG @gormless87 Here is the dev link for your review on AWS dev frontend deployment.

ArogeG commented 1 year ago

@ianliuwk1019, thanks for the detailed comment above. Please can you provision my IDIR: to login to the app or is there a TEST Idir, I can used to login?

basilv commented 1 year ago

Hi @ArogeG your IDIR should just work if I recall correctly, nothing in the front-end or API backend checks for access (authorization) yet,