{"id":19029977,"url":"https://github.com/guardian/pan-domain-authentication","last_synced_at":"2025-08-21T05:31:59.063Z","repository":{"id":19881194,"uuid":"23145374","full_name":"guardian/pan-domain-authentication","owner":"guardian","description":"Helper to provide a common federated authentication for all services within a domain (AKA Panda 🐼)","archived":false,"fork":false,"pushed_at":"2025-07-08T13:40:22.000Z","size":1550,"stargazers_count":11,"open_issues_count":25,"forks_count":9,"subscribers_count":20,"default_branch":"main","last_synced_at":"2025-08-18T17:00:08.071Z","etag":null,"topics":["authentication","oauth2","pan-domain-authentication","production"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/guardian.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2014-08-20T10:51:32.000Z","updated_at":"2025-07-30T16:43:38.000Z","dependencies_parsed_at":"2023-10-05T02:29:40.800Z","dependency_job_id":"2bb52d0e-4378-4a0d-8411-28df120bcb5a","html_url":"https://github.com/guardian/pan-domain-authentication","commit_stats":{"total_commits":331,"total_committers":34,"mean_commits":9.735294117647058,"dds":0.8006042296072508,"last_synced_commit":"a0adfde6e113a1bf0c8c62e6d52c1c77d8dfe73c"},"previous_names":[],"tags_count":92,"template":false,"template_full_name":null,"purl":"pkg:github/guardian/pan-domain-authentication","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guardian%2Fpan-domain-authentication","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guardian%2Fpan-domain-authentication/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guardian%2Fpan-domain-authentication/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guardian%2Fpan-domain-authentication/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/guardian","download_url":"https://codeload.github.com/guardian/pan-domain-authentication/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guardian%2Fpan-domain-authentication/sbom","scorecard":{"id":447957,"data":{"date":"2025-08-11","repo":{"name":"github.com/guardian/pan-domain-authentication","commit":"0e05609f8e82465954b671f8658f4a364a394f53"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5.6,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":6,"reason":"8 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 6","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":5,"reason":"Found 8/16 approved changesets -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: jobLevel 'contents' permission set to 'write': .github/workflows/release.yml:9","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/sbt-dependency-graph.yaml:30","Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Warn: no topLevel permission defined: .github/workflows/release.yml:1","Warn: no topLevel permission defined: .github/workflows/sbt-dependency-graph.yaml:1"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":4,"reason":"dependency not pinned by hash detected -- score normalized to 4","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/guardian/pan-domain-authentication/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/guardian/pan-domain-authentication/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/guardian/pan-domain-authentication/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:8: update your workflow using https://app.stepsecurity.io/secureworkflow/guardian/pan-domain-authentication/release.yml/main?enable=pin","Info:   2 out of   3 GitHub-owned GitHubAction dependencies pinned","Info:   2 out of   5 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Branch-Protection","score":4,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'main'","Info: 'force pushes' disabled on branch 'main'","Warn: 'branch protection settings apply to administrators' is disabled on branch 'main'","Warn: 'stale review dismissal' is disabled on branch 'main'","Warn: required approving review count is 1 on branch 'main'","Warn: codeowners review is not required on branch 'main'","Warn: 'last push approval' is disabled on branch 'main'","Warn: 'up-to-date branches' is disabled on branch 'main'","Info: status check found to merge onto on branch 'main'","Info: PRs are required in order to make changes on branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-9hjg-9r4m-mvj7"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":10,"reason":"SAST tool is run on all commits","details":["Info: all commits (22) are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T07:23:47.403Z","repository_id":19881194,"created_at":"2025-08-19T07:23:47.404Z","updated_at":"2025-08-19T07:23:47.404Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271430756,"owners_count":24758365,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","oauth2","pan-domain-authentication","production"],"created_at":"2024-11-08T21:16:01.478Z","updated_at":"2025-08-21T05:31:59.050Z","avatar_url":"https://github.com/guardian.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pan Domain Authentication \n\n[![pan-domain-auth-core Scala version support](https://index.scala-lang.org/guardian/pan-domain-authentication/pan-domain-auth-core/latest-by-scala-version.svg?platform=jvm)](https://index.scala-lang.org/guardian/pan-domain-authentication/pan-domain-auth-core)\n\n* This repo - General docs \u0026 Scala implementation\n* [pan-domain-node](https://github.com/guardian/pan-domain-node) - Typescript implementation\n\nPan domain authentication provides distributed authentication for multiple webapps running in the same domain. Each\napplication can authenticate users against an OAuth provider and store the authentication information in a common cookie.\nEach application can read this cookie and check if the user is allowed in the specific application and allow access accordingly.\n\nThis means that users are only prompted to provide authentication credentials once across the domain and any inter-app\ninteractions (e.g javascript cross-origin requests) can be easily secured.\n\n## How it works\n\nThe library can be used in two ways:\n\n - **Verify**: read the Panda cookie and check whether the user is valid for the request\n - **Issue**: as above but redirecting the user to the OAuth provider to authenticate if the cookie is not present or expired\n\nSimply verifying the cookie is useful for APIs that cannot provide a user-facing OAuth dance to acquire credentials. It is also\nuseful to minimise the parts of your application that have to have knowledge of the private key.\n\nTo ensure the cookie is not tampered with, public/private key pair encryption is used. An issuing application signs the cookie\nusing the private key and both verifying and issuing applications verify using the public key.\n\nThe cookie contains an expiry time generated at issue after which the user should be redirected to the OAuth provided again.\n\nAll OAuth 2.0 compliant providers are supported. There is additional support for verifying that the user has two-factor login\nenabled when using Google as the provider.\n\nEach authenticated request that an application receives should be checked to see if there is a auth cookie.\n\n* If the cookie is not present then the user should be sent to the OAuth provider for authentication. Upon their return the user\ninformation should be checked and if the user is allowed in the app then the shared cookie should be set marking the user as valid\nin the application.\n\n* If there is a cookie but the cookie does not indicate that the user is valid then the user should be validated for the application.\nThis is an application-specific concern such as verifying the email address or checking two-factor is enabled. If they are valid then the\ncookie should be updated to indicate this or an error page displayed.\n\n* If there is a cookie and it indicates the user is valid in this application then the request should be processed as normal.\n\n* if there is a cookie but it indicates the the authentication is expired then the user should be sent off to the provider to renew their session.\nOn their return the existing cookie is updated with the new expiry time.\n\n## What's provided\n\nPan domain auth is split into 6 modules.\n\nThe [pan-domain-auth-verification](#to-verify-logins) library provides the basic functionality for signing and verifying login cookies in Scala.\nFor JVM applications that only need to *VERIFY* an existing login (rather than issue logins themselves) this is the library to use.\n\nThe `pan-domain-auth-core` library provides the core utilities to load settings, create and validate the cookie and\ncheck if the user has multi-factor auth turned on when using Google as the provider.\n\nThe [pan-domain-auth-play_2-8, 2-9 and 3-0](#if-your-application-needs-to-issue-logins) libraries provide an implementation for play apps. There is an auth action\nthat can be applied to the endpoints in your application that will do checking and setting of the cookie and will give you the OAuth authentication\nmechanism and callback. This is the only framework specific implementation currently (due to play being the framework predominantly used at The\nGuardian), this can be used as reference if you need to implement another framework implementation. This library is for applications\nthat need to be able to issue and verify logins which is likely to include user-facing applications.\n\nThe [pan-domain-node](https://github.com/guardian/pan-domain-node) library provides an implementation of *verification only* for node apps.\n\nThe `pan-domain-auth-example` provides an example Play 2.9 app with authentication. Additionally the nginx directory provides an example\nof how to set up an nginx configuration to allow you to run multiple authenticated apps locally as if they were all on the same domain which\nis useful during development.\n\nThe [panda-hmac](#to-verify-machines) libraries build on pan-domain-auth-play to also verify machine clients,\nwho cannot perform OAuth authentication, by using HMAC-SHA-256.\n\n## Requirements\n\nIf you are adding a new application to an existing deployment of pan-domain-authentication then you can skip to\n[Integrating With Your App](#integrating-with-your-app)\n\n* At least one webapp running on subdomains of a single domain (e.g. app1.example.com and app2.example.com)\n\n* The apps must be using https - the cookie set by pan domain auth are set to secure and http only\n\n* An OAuth provider to use for authentication\n\n## Setting up your domain configuration\n\nPanda is compatible with all OAuth2 providers, automatically discovering endpoints using a\n[discovery document](https://tools.ietf.org/html/draft-ietf-oauth-discovery-06).\n\n### AWS Cognito\n\nFollow these steps to create a new [Cognito User Pool](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html).\nThis allows you to manage your users entirely within AWS.\n\nYou will need:\n\n- The AWS CLI and credentials for your AWS account\n- An OAuth callback URL for each application that will be issuing logins\n\nRun the following commands:\n\n- Deploy the [CloudFormation Template](./cognito/cognito.yaml)\n- Generate settings and public/private keys\n  - `./cognito/generate-settings.sh ${CLOUDFORMATION_STACK} ${REGION}` \n  - This will also upload them to the configuration bucket\n- Add users\n  - `./cognito/add-user.sh ${CLOUDFORMATION_STACK} ${USER_EMAIL} ${REGION}`\n  - They will receive an email invite with a temporary password\n\n### Generic OAuth2 Provider \n\nYou will need:\n\n* An AWS S3 bucket where the configuration for your domain will live\n* Both a [Client ID and secret](https://www.oauth.com/oauth2-servers/client-registration/client-id-secret/) are required\n  * An OAuth [discovery document](https://tools.ietf.org/html/draft-ietf-oauth-discovery-06) is required\n    * Example Google: `https://accounts.google.com/.well-known/openid-configuration`\n    * Example AWS Cognito: `https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_nW3FKqRh0/.well-known/openid-configuration`\n  * You must also configure the callback URLs with your provider, one for each application under the domain that will be issuing logins.\n\nThe configuration file is named for the domain and is a simple properties style file. A good naming convention to follow\nis for apps on the `*.example.com` domain to name the file `example.com.settings`. The contents of the file would look something like this:\n\n``` ini\npublicKey=example_key\nprivateKey=example_key\ncookieName=exampleAuth\n\nclientId=example_oauth_client_id\nclientSecret=example_oauth_secret\norganizationDomain=example.com\n\ngoogleServiceAccountId=serviceAccount@developer.gserviceaccount.com\ngoogleServiceAccountCert=name_of_cert_in_bucket.p12\ngoogle2faUser=an.admin@example.com\nmultifactorGroupId=group@2fa_admin_user\n```\n  \nThere is a corresponding (publically available) file called example.com.settings.public. \nThe contents of the file looks like:\n \n``` ini\npublicKey=example_key\n```\n\n* **cookieName** - the name of the cookie. This should be unique to each top-level domain.\n\n* **clientId** - this is the OAuth client id for the provider you are authenticating against\n\n* **clientSecret** - this is the OAuth secret for the provider\n\n* **organizationDomain** - OPTIONAL: this is the domain where users are registered, for services which support it. If user's emails are in the format `user@example.com`, then this should be set to `example.com`.\n  * Known services with support: \n    * Google\n\n* **googleServiceAccountId, googleServiceAccountCert, google2faUser and multifactorGroupId** - these are optional parameters for using a group based 2 factor auth verification\n\n* **privateKey** - this is the private key used to sign the cookie\n\n* **publicKey** - this is the public key used to verify the cookie\n\n### Rotating Keys\n\n**Guardian Devs**: See the [Panda key-rotation Guide](https://docs.google.com/document/d/1haVnQ9D8zNYUU-fOfkudPC1WpPGrlelLygd8V7xb3eQ/edit?usp=sharing)\nfor Guardian-specific details of where config details are stored, etc.\n\nTo avoid disruption to users, rotating keys requires 3 distinct settings updates, with pauses between each one. First\nobtain a copy of the current settings file (eg `current-from-s3.settings`), then use the sbt console to run\nthe `CryptoConfForRotation` Scala script on that `.settings` file to generate a new RSA 4096 keypair and the new\nrequired config files for each step:\n\n```\nkey-rotation/run current-from-s3.settings\n```\n\n3 new partial `.settings` files will be created, providing _just_ the updated crypto settings - you'll need to\nedit them into the existing `current-from-s3.settings` \u0026 `current-from-s3.settings.public` files before uploading\nthose updates:\n\n* 1.rotation-upcoming.settings - give this 2 minutes of settling time \n* 2.rotation-in-progress.settings - give this at least 1 hour of settling time\n* 3.rotation-complete.settings\n\n## Integrating with your Scala app\n\n### To verify logins\n\nAdd the verification library as an SBT dependency to your project, taking care to use the latest version:\n\n[![pan-domain-auth-verification Scala version support](https://index.scala-lang.org/guardian/pan-domain-authentication/pan-domain-auth-verification/latest-by-scala-version.svg?platform=jvm)](https://index.scala-lang.org/guardian/pan-domain-authentication/pan-domain-auth-verification)\n\n```scala\nlibraryDependencies += \"com.gu\" %% \"pan-domain-auth-verification\" % \"\u003c\u003cLATEST_VERSION\u003e\u003e\"\n```\n\nFollow the example code provided [here](pan-domain-auth-example/app/VerifyExample.scala)\n\nConfiguration settings are read from an S3 bucket. Follow the steps below if you do not yet have configuration set up.\n\n### If your application needs to issue logins\n\nAdd the core library as an SBT dependency to your project, taking care to use the latest version.\nIf you are building your application using the Play framework, use the Play integration.\nPlay versions 2.9 and 3.0 are supported only on Scala 2.13.\nPlay version 2.8 is supported on Scala 2.12 and 2.13.\nPlay version 2.7 is supported up until v1.3.0.\nPlay version 2.6 is supported up until v0.9.2.\n\n[![pan-domain-auth-core Scala version support](https://index.scala-lang.org/guardian/pan-domain-authentication/pan-domain-auth-core/latest-by-scala-version.svg?platform=jvm)](https://index.scala-lang.org/guardian/pan-domain-authentication/pan-domain-auth-core)\n\n```scala\nlibraryDependencies += \"com.gu\" %% \"pan-domain-auth-core\" % \"\u003c\u003cLATEST_VERSION\u003e\"\n```\n\nor\n\n```scala\n// pick the version corresponding to your app's version of Play\nlibraryDependencies += \"com.gu\" %% \"pan-domain-auth-play_2-8\" % \"\u003c\u003cLATEST_VERSION\u003e\"\nlibraryDependencies += \"com.gu\" %% \"pan-domain-auth-play_2-9\" % \"\u003c\u003cLATEST_VERSION\u003e\"\nlibraryDependencies += \"com.gu\" %% \"pan-domain-auth-play_3-0\" % \"\u003c\u003cLATEST_VERSION\u003e\"\n```\n\nExample code for the Play Framework is provided [here](./pan-domain-auth-example). In particular:\n\n- [di.scala](./pan-domain-auth-example/di.scala) shows how to construct the settings refresher\n- [ExampleAuthActions](./pan-domain-auth-example/controllers/ExampleAuthActions.scala) shows how to implement user validation\n- [AdminController](./pan-domain-auth-example/controllers/AdminController.scala) shows how to built authenticated request handlers\n\nEnsure you pick the correct request handler for your needs:\n\n* `AuthAction` is used for endpoints that the user requests and will redirect unauthenticated users to the OAuth provider for authentication.\n  Use this for standard page loads etc.\n\n* `ApiAuthAction` is used for api ajax / xhr style requests and will not redirect to the OAuth provider. This action will either process\n  the action or return an error code that can be processed by your client javascript (see section on handling expired logins in a single\n  page webapp).\n\n  A grace period on expiry can be set by adding a `apiGracePeriod`. This is useful for when browsers have third party cookies disabled\n  and reauthenticaiton solutions like [pandular](https://github.com/guardian/pandular) break due to cookies being blocked on `window.open`\n  or `iframe` requests. During this period we are hopeful of the user refreshing or revisiting the application through a standard browser\n  request thus triggering off a reauthentication.\n\n  The response codes are:\n\n    * **401** - user not authenticated - probably tricky to get this response as presumably the user has already loaded a page that would have\n            logged them in\n\n    * **403** - not authorised - occurs then the user is authenticated but not valid in this app, this can happen when making cross app CORS\n            requests\n\n    * **419** - authorisation expired - occurs when the authorisation with the OAuth provider has expired (eg 1 hour for Google).\n                You will need to re auth with the provider. This can typically be done transparently on the next page load request.\n\n  See also [Customising error responses for an authenticated API]().\n\nExample Scala code is not yet provided for web frameworks other than Play.\n\n\n### Customising error responses for an authenticated API\n\nThe default `ApiAuthAction` error responses returns sensible status codes but no body.\n\nTo customise the responses (code and body) of an authenticated API,\nyou can provide your own implementation of the `AbstractApiAuthAction`\ntrait that provides the various abstract result properties:\n\n``` scala\nobject VerboseAPIAuthAction extends AbstractApiAuthAction {\n  val notAuthenticatedResult: Result = Unauthorized(errorResponse(\"Not authenticated\"))\n  val invalidCookieResult: Result    = notAuthenticatedResult\n  val expiredResult: Result          = Forbidden(errorResponse(\"Session expired\"))\n  val notAuthorizedResult: Result    = Forbidden(errorResponse(\"Not authorized\"))\n\n  private def errorResponse(msg: String) = Json.obj(\"error\" -\u003e msg)\n}\n```\n\n\n### Using Google group based 2-factor authentication validation\n\nSome applications may require that a multifactor authentication is used when authenticating a user. Since it is not possible to tell if this happened\nfrom the standard callback this is checked by asserting that the user is in a Google group that enforces 2 factor auth (this was the workaround suggested\nby Google themselves when we asked about checking 2fa). Since the group is likely set up within an apps for domains setup and not accessible to everyone\nchecking the 2fa group uses different Google credentials from the main auth.\n\nTo configure multifactor checking you will need to create a service account that can access the Google directory api,\nsee [directory API docs](https://developers.google.com/admin-sdk/directory/v1/guides/delegation). once this is configured fill in all the following properties\nin the domain's property file and upload the service accounts cert to the s3bucket. If you do not wish to use this feature just omit the\nproperties:\n\n* **googleServiceAccountId** - the service account email that is set up to allow access to the directory API\n* **googleServiceAccountCert** - the name within the bucket of the certificate used to validate the service account\n* **google2faUser** - the admin user to connect to the directory api as, this is not the service account user but a user in your org who is authorised to access group information\n* **multifactorGroupId** - the name of the group that indicates and enforces that 2fa is turned on\n\n### To verify machines\n\nAdd a dependency on the correct version of `pan-domain-auth-play` and configure to allow authentication of users using OAuth 2. Then, adding support should be as simple as adding a dependency on the relevant panda-hmac-play library, and mixing `HMACAuthActions` into your controllers.\n\nExample:\n\n```scala\nimport com.gu.pandahmac.HMACAuthActions\n\n// ...\n\n@Singleton\nclass MyController @Inject() (\n    override val config: Configuration,\n    override val controllerComponents: ControllerComponents,\n    override val wsClient: WSClient,\n    override val refresher: InjectableRefresher\n) extends AbstractController(controllerComponents)\n    with PanDomainAuthActions\n    with HMACAuthActions {\n\n  override def secretKeys = List(\"currentSecret\") // You're likely to get your secret from configuration or a cloud service like AWS Secrets Manager\n\n  def myApiActionWithBody = APIHMACAuthAction.async(circe.json(2048)) { request =\u003e \n    // ... do something with the request\n  }\n\n  def myRegularAction = HMACAuthAction {}\n\n  def myRegularAsyncAction = HMACAuthAction.async {}\n}\n```\n\n#### Setting up a machine client\n\nThere are example clients for Scala, Javascript and Python in the `hmac-examples/` directory.\n\nEach client needs a copy of the shared secret, defined as \"currentSecret\" in the controller example above.\nEach request needs a standard (RFC-7231) HTTP Date header, and an authorization digest that is calculated like this:\n\n1. Make a \"string to sign\" consisting of the HTTP Date and the Path part of the URI you're trying to access, \nseperated by a literal newline (unix-style, not CRLF)\n2. Calculate the HMAC digest of the \"string to sign\" using the shared secret as a key and the HMAC-SHA-256 algorithm\n3. Base64 encode the binary output of the HMAC digest to get a random-looking string\n4. Add the HTTP date to the request headers with the header name **'X-Gu-Tools-HMAC-Date'**\n5. Add another header called **'X-Gu-Tools-HMAC-Token'** and set its value to the literal string **HMAC** followed by a\n space and the digest, like this: `X-Gu-Tools-HMAC-Token: HMAC boXSTNumKWRX3eQk/BBeHYk`\n6. Send the request and the server should respond with a success.\n7. The default allowable clock skew is 5 minutes, if you have problems then this is the first thing to check.\n\n#### Testing HMAC-authenticated endpoints in isolation\n\n[Postman](https://www.postman.com/) is a common environment for testing HTTP requests. We can add a [pre-request script](https://learning.postman.com/docs/writing-scripts/pre-request-scripts/) that automatically adds HMAC headers when we hit send.\n\n\u003cdetails\u003e\n\u003csummary\u003ePre-request script\u003c/summary\u003e\n  \n```js\nconst URL = require(\"url\");\n\nconst uri = pm.request.url.toString();\nconst secret = \"Secret goes here :)\";\n\nconst httpDate = new Date().toUTCString();\nconst path = new URL.parse(uri).path;\nconst stringToSign = `${httpDate}\\n${path}`;\nconst stringToSignBytes = CryptoJS.enc.Utf8.parse(stringToSign);\nconst secretBytes = CryptoJS.enc.Utf8.parse(secret);\n\nconst signature = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(stringToSignBytes, secretBytes));\nconst authToken = `HMAC ${signature}`;\n\npm.request.headers.add({ key: 'X-Gu-Tools-HMAC-Date', value: httpDate });\npm.request.headers.add({ key: 'X-Gu-Tools-HMAC-Token', value: authToken });\n```\n\n\u003c/details\u003e\n\n\n### Dealing with auth expiry in a single page webapp\n\nIn a single page webapp there will typically be an initial page load and then all communication with the server will be initiated by JavaScript.\nThis causes problems when the auth session expires as you can't redirect the request to the OAuth provider. To work around this\nall ajax type requests should return 419 responses on auth session expiry and this should be handled by the JavaScript layer.\n\nSee also the helper [panda-session](https://github.com/guardian/panda-session) JavaScript library.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguardian%2Fpan-domain-authentication","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fguardian%2Fpan-domain-authentication","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguardian%2Fpan-domain-authentication/lists"}