{"id":21436757,"url":"https://github.com/modfin/twofer","last_synced_at":"2025-07-14T14:33:22.058Z","repository":{"id":45542195,"uuid":"238508952","full_name":"modfin/twofer","owner":"modfin","description":null,"archived":false,"fork":false,"pushed_at":"2024-05-30T11:04:58.000Z","size":304,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-06-20T19:20:34.051Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/modfin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSES_DEP","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-05T17:30:15.000Z","updated_at":"2024-05-30T11:05:02.000Z","dependencies_parsed_at":"2024-06-20T18:52:55.662Z","dependency_job_id":null,"html_url":"https://github.com/modfin/twofer","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modfin%2Ftwofer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modfin%2Ftwofer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modfin%2Ftwofer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modfin%2Ftwofer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/modfin","download_url":"https://codeload.github.com/modfin/twofer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225981808,"owners_count":17554923,"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","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":[],"created_at":"2024-11-23T00:15:11.897Z","updated_at":"2025-07-14T14:33:22.031Z","avatar_url":"https://github.com/modfin.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Twofer\nA stateless service implementing some two factor authentication methods, so life is gets somewhat easier. \n \n ## General \n Twofer is intended to be deployed within your stack and not be accessible directly from the outside. \n Instead Twofer can be configured to expose gRPC APIs in order to handle different factors in your multi part authentication \n scheme.\n \n \n## E-ID\nTwofer support Swedish BankId as Electronic identification. This can be used for signup in order to collect the \n identity of the user, as a factor in a authentication scheme or for collecting signatures.\n \n### API \nThe gRPC API for Authentication and Signatures abstracts the provider that is being used and unifies the way of handling \nE-ID.  \n\nThere is 5 method calls\n* `GetProviders` - Returns a list of active Eid providers registered. eg. BankId\n* `AuthInit` - Initiates a Authentication request  \n* `SignInit` - Initiates a Signature request\n* `Peek` - Returns the current status of a Auth or a Sign request\n* `Collect` - Waits for a Auth or a Sign request to finish and returns the result\n* `Cancel` - Cancels an ongoing request \n\n### Swedish BankID - [bankid.com](https://www.bankid.com/bankid-i-dina-tjanster/rp-info)\nTwofer is in the context of BankID considered a Relying party.\n\n#### For testing\n* Download a [SSL certificate for test](https://developers.bankid.com/test-portal/test-information).\n* Extract the pem file `openssl pkcs12 -in FPTestcert3_20200618.p12 -out bank_id_all.pem -nodes` (password: qwerty123)\n* From bank_id_all.pem, extract Private Key portion into  `bank-id-key.pem`\n* From bank_id_all.pem, extract Certificate portion into  `bank-id-cert.pem` \n* From [documentation](https://developers.bankid.com/getting-started/backend/environments) copy Root CA pem (section 8) into `bank-id-rootca.pem`\n\n**Config**\nWhen starting twofer add the following environment variables\n```bash\nEID_BANKID_ENABLE=true\nEID_BANKID_URL=https://appapi2.test.bankid.com\n\n## Used to authenticate BankID servers servers towards twofer\n## EID_BANKID_ROOT_CA_PEM can be used to load pm directly from file\nEID_BANKID_ROOT_CA_PEM_FILE=/path/to/bank-id-rootca.pem  \n\n## Used to authenticate your account towards BankID\n## EID_BANKID_CLIENT_CERT can be used to load pm directly from file\nEID_BANKID_CLIENT_CERT_FILE=/path/to/bank-id-cert.pem    \n\n## Used to authenticate your account towards BankID\n## EID_BANKID_CLIENT_KEY can be used to load pm directly from file\nEID_BANKID_CLIENT_KEY_FILE=/path/to/bank-id-key.pem      \n```\n\n**Use**\n* Go to https://demo.bankid.com/ and register a test account.\n* Use gRPC client.\n\n## OTP\nTOTP and HOTP is often part of a multi factor scheme and while this is often not hard to implement, it might be harder \nto protect and there are a few consideration when implementing it. There for twofer includes a OTP service that helps \nwith enrollment and authentication\n\n**State**\nThe OTP relies on a state in order to verify a user, this means the user must persist the userblob when provided. This \nsince the state must be passed to twofer when called\n\n**Config**\n* Generate a AES key, eg `$ echo 1:aes:$(openssl rand -base64 16)`\n\n```bash\nOTP_ENABLE=\"true\"\n\n# Used to seal and open the uri in order not to stor it in plain text\n# The latest key version is always used, this means that on each Auth the returning blob\n# will be encrypted using this key, this enables you to upgrade keys that protects the users credentials\nOTP_ENCRYPTION_KEY=\"1:aes:Hg44JefQsFJMI1F0zhWMpw== 2:aes:xzK4KyrOUo45VfFiF9vijw==\"  \n\n# How many attempts verification attempts can be made for the same user a minute\nOTP_RATE_LIMIT=10 # Default: 10 `\n\n# If counter mode is used, How many OTPs ahead is checked\nOTP_SKEW_COUNTER=5 # Default: 5 `\n\n# If time mode is used, How many OTPs forward and backwards in time is checked\nOTP_SKEW_TIME=1 # Default: 1`\n```\n\n**Use**\n* gRPC `Enroll`, persist returning userBlob in a database coupled with the user\n* gRPC `Auth`, update/persist returning userBlob in database coupled with the user\n\n \n## WebAuthn\nWebAuthn is a protocol to verify a user through, among other thins, the browser by eg. using a FIDO2 key.\nSee https://webauthn.io/ or https://www.w3.org/TR/webauthn/ for more information. While \n\n**State**\nThe WebAuthn relies on a state in order to verify a user, this means the user must persist the userblob when provided. This \nsince the state must be passed to twofer when called\n\n**Config**\n* Generate a HMAC key, eg `$ echo $(openssl rand -base64 32)`\n\n```bash\nWEBAUTHN_ENABLED=true\nWEBAUTHN_RP_ID=localhost\nWEBAUTHN_RP_DISPLAYNAME=localhost\nWEBAUTHN_RP_ORIGIN=http://localhost:8080\nWEBAUTHN_HMAC_KEY=+SoWOS6kLTe8OOVTBXnQ+lMAsUH0hncsnCJUQ2javqw=\n\n# Can be discouraged/proffered/required \nWEBAUTHN_USER_VERIFICATION=discouraged # Default: discouraged `\n\n# How many api calls can be made for the same user per minute\nWEBAUTHN_RATE_LIMIT=10 # Default: 10\n\n# Once a session is issues, for how long is it valid\nWEBAUTHN_TIMEOUT=60s # Default: 60s\n```\n\n**Use**\n* gRPC `EnrollInit`, pass in the current userBlob, if it exist. It creates a session and json, the json shall be passed to the frontend for the authenticator to interact with. \n* gRPC `EnrollFinal`, the session that from EnrollInit creates shall be passed coupled with the signature (the frontend response). \n  This returns a userBlob on success, this blob should be persisted and used in the auth requests. If a user blob existed prior to the enrollment it \n  shall be replaced by the returning one. This allows for a user to have multiple authenticators.\n* gRPC `AuthInit`, pass in the current userBlob. It creates a session and json, the json shall be passed to the frontend for the authenticator to interact with.\n* gRPC `AuthFinal`, the session that from AuthInit creates shall be passed coupled with the signature (the frontend response). \n  If successful it returns valid = true\n}\n\n\n## QR\nSince both BankID and OTP have a QR-code components, a gRPC api is included which turns test in to a png QR code image\n\n**Use**\n* gRPC `Generate`\n\n## Dev\nRelease new versions of the Docker image onto [Dockerhub](https://hub.docker.com/r/modfin/twofer)  \n```\ndocker login --username=yourhubusername # then enter pass\n./docker-build-push.sh\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodfin%2Ftwofer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmodfin%2Ftwofer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodfin%2Ftwofer/lists"}