{"id":18834355,"url":"https://github.com/pzl/uua","last_synced_at":"2025-10-27T01:03:17.753Z","repository":{"id":57551947,"uuid":"189081528","full_name":"pzl/uua","owner":"pzl","description":"microservice for user authentication and authorization","archived":false,"fork":false,"pushed_at":"2019-06-28T17:16:41.000Z","size":180,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-12-30T07:42:05.882Z","etag":null,"topics":["auth","authentication","encryption","go","golang","jwt","microservice","token"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pzl.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":null,"security":null,"support":null}},"created_at":"2019-05-28T18:25:18.000Z","updated_at":"2021-10-20T14:20:14.000Z","dependencies_parsed_at":"2022-09-26T18:41:40.943Z","dependency_job_id":null,"html_url":"https://github.com/pzl/uua","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/pzl%2Fuua","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pzl%2Fuua/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pzl%2Fuua/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pzl%2Fuua/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pzl","download_url":"https://codeload.github.com/pzl/uua/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239768923,"owners_count":19693764,"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":["auth","authentication","encryption","go","golang","jwt","microservice","token"],"created_at":"2024-11-08T02:12:05.822Z","updated_at":"2025-10-27T01:03:17.679Z","avatar_url":"https://github.com/pzl.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"Universal User Auth\n=====================\n\n[![GoDoc](https://godoc.org/github.com/pzl/uua?status.svg)](http://godoc.org/github.com/pzl/uua)\n\n`uua` is a Universal User Auth microservice. \n\nYou provide it with a token request (login): username, secret (password, key, something else). And it responds with an encrypted+signed token. When the service is provided with a token, it validates the token and returns it's content (user, app, etc).\n\nThis is basically a [JWT](https://jwt.io/), or JWE flavor since it's encrypted, without the header. \n\nFor using `uua` as a library, and not a microservice, see [As a Library](#as-a-library)\n\nQuickstart\n-----------\n\n#### Create Users\n\n```sh\necho \"\nauth:\n  password:\" \u003e uua.yaml\necho \"    some-user-name: \" $(mkpass) \u003e\u003e uua.yaml # change to actual user's name, repeat for other users\n```\n\n(`mkpass` is a utility that comes with `uua`. It generates argon2-hashed passwords with a random salt.)\n\nSee [Set Authentication](#set-authentication)\n\n#### Run\n\n```sh\nuua -c uua.yaml\n```\n\n#### Use\n\n**Get Token**\n\n```sh\ncurl -k -XPOST -d '{\"user\":\"some-user-name\", \"pass\":\"yourpass\"}' localhost:6089/api/v1/login\n# {\"token\":\"64vly...\"}\n```\n\n\n**Auth with Token**\n\n```sh\ncurl -k -XPOST -d \"64vly...\" localhost:6089/api/v1/verify\n# {\"token\":{\"v\":1,\"u\":\"some-user-name\",\"g\":1,\"a\":\"\",\"e\":1560876042}, \"valid\":true }\n```\n\nsee [Authenticating Users](#authenticating-users)\n\n\n\nFor the less-quick start, see [Integrating with Other Apps](#integrating-with-other-apps).\n\n\nRecommended Configuration\n--------------------------\n\n### Use Static credentials\n\nYou can run `uua` with auto-generated credentials (RSA key, password+salt), but it will generate new ones each time it restarts. This invalidates all previous tokens, if even one of them is different. Using a temporary generated credential for any of these, only allows for tokens to be valid until the next restart.\n\nThis is OK for temporary situations. But for any sort of persistence beyond restarts, or load balancing between multiple `uua` servers, you need static credentials in your config.\n\n**RSA Key**\n```sh\nssh-keygen -t rsa -f sign.key # create a private RSA key\necho \"sign-key: sign.key\" \u003e\u003e uua.yaml\n```\n\n**Password \u0026 Salt**\n\n```sh\necho \"pass: abc123\" \u003e\u003e uua.yaml\necho \"salt: xyz456\" \u003e\u003e uua.yaml\n```\n\n###  Enable SSL\n\n**Generate self-signed cert \u0026 key**, (if you don't already have these from somewhere else, or are using Let's Encrypt, etc)\n\n```sh\nopenssl req -x509 -nodes -newkey rsa:2048 -keyout server.key -out server.crt -days 3650\n```\n\n**Add to Config**\n\n```sh\necho \"\nssl-key: server.key\nssl-cert: server.crt\n\" \u003e\u003e uua.yaml\n```\n\nThen you can continue to run as normal: `uua -c uua.yaml`. and POST with:\n\n```sh\ncurl -k -XPOST -d '{\"user\":\"yourusername\", \"pass\":\"yourpass\"}' https://localhost:6089/api/v1/login\n```\n\n\nIntegrating with Other Apps\n---------------------------\n\nBecause you probably want something to authenticate _to_. There are two methods of using UUA:\n\n- [As an HTTP Microservice](#as-a-service)\n- [As a Go Library](#as-a-library)\n\n### As a Service\n\nYou can use UUA as an authentication microservice for your apps or microservices. \n\n1. Have your app prompt the user for username + password (until other auth methods are supported)\n1. Your app will POST the credentials to `UUA:/api/v1/login`. `HTTP 401` on bad credentials, and a token otherwise\n1. Associate and save the token with the user (somewhere safe, this token grants access as the user)\n1. On next use, search that same place for a token\n    + If no token, go back to step 1\n    + POST to `UUA:/api/v1/verify` with the token as the body\n    + if `401`, go back to step 1. Otherwise, you will receive username + some metadata. You can proceed as `user`\n\nFor more details on the HTTP endpoints see [Authenticating Users](#authenticating-users)\n\n\n### As a Library\n\nUUA provides an HTTP server, but it is not required. You may import and use `uua` as a golang library. Performing authentication is up to your app. the `uua` library's purpose is to:\n\n- Create and serialize tokens\n- Verify that a serialized token string is authentic, and valid\n\nSo your app must implement whatever authentication steps are required before granting a token to a user (user/pass, iris scan, device, etc).\n\n**Creating a token**\n\n```go\nimport \"time\"\nimport \"github.com/pzl/uua\"\n\nconst MY_APP = \"demo\"\nconst GENERATION = 1\nconst EXPIRATION = 3 * time.Hour\n\nfunc main() {\n\n    // secret material required to create tokens:\n    // *rsa.PrivateKey (signing)\n    // password + salt (symmetric encrypt token)\n    key := loadPrivateKey() // *rsa.PrivateKey\n    pass, salt := loadServerEnc()\n\n    sec := uua.Secrets{\n        Key:  key,\n        Pass: pass,\n        Salt: salt,\n    }\n\n    for {\n        r := getRequest() // whatever user request or login your app has\n\n        // validate credentials. This is up to your app to do. Check an email+pass perhaps\n        if ! validCredentials(r) {\n            respond(r, false, \"bad credentials\")\n            continue\n        }\n\n        // create a token and serialize, encrypt, \u0026 sign it\n        t, err := uua.New(r.Username, MY_APP, GENERATION, EXPIRATION).Encode(sec)\n        if err != nil { //handle, log, whatever\n            continue\n        }\n\n        respond(r, true, t) // send token string to the user, for storage\n    }\n}\n```\n\n**Validating a Token**\n\n```go\npackage main\n\nimport \"fmt\"\nimport \"github.com/pzl/uua\"\n\nconst MY_APP = \"demo\"\nconst GENERATION = 1 // tokens must be of the same generation to be valid. Increment to revoke\n// see Revocation at the bottom of the Readme for more info\n\nfunc main() {\n    // same secrets as above\n    sec := uua.Secrets{\n        Pass: pass,\n        Salt: salt,\n        Key:  key,\n    }\n\n    // with a user action, receive or read token string from somewhere\n    ts := getTokenFromRequest() \n\n    token, err := uua.Validate(ts, sec, GENERATION)\n    if err != nil { // invalid token (time expired, invalid signature, old generation)\n        valid(false, err.Error())\n        return\n    }\n\n    // valid `token` object. Can check token.App if desired to route a user someplace\n    // or to make sure tokens used for some microservices are restricted to only those\n\n    // optional\n    if token.App != MY_APP {\n        valid(false, fmt.Sprintf(\"tokens from %s are not valid at %s\", token.App, MY_APP))\n        return\n    }\n\n    valid(true, token.User) // allow access as this user\n}\n\n```\n\nSee [godoc](http://godoc.org/github.com/pzl/uua) for more\n\nConfig\n-------\n\nto avoid specifying credentials on the command line, you can use ENV vars, or a config file:\n\n```sh\necho \"\nsign-key: private_rsa\npass: encpass\nsalt: x3*h9dw0e\n\" \u003e config.yml # create a config file\nCONFIG_FILE=config.yml uua  # or -c config.yml\n```\n\nThe parameters are processed in the following precedence, highest first:\n\n1. Command line arg (e.g. `-s mysalt56`)\n1. Env var (e.g. `SALT=somesalt`)\n1. Config file (via `-c FILEPATH`)  (supported extensions: `json, toml, yaml, yml` for all config files)\n1. Config file (via `$CONFIG_FILE` env)\n1. Config file (via default search paths)\n1. Default values (generation and listen address have defaults)\n\nThe arguments are:\n\n```\n  -p, --pass     string   symmetric encryption password               ENV: PASS\n  -s, --salt     string   symmetric encryption salt                   ENV: SALT\n  -r, --rsa      string   RSA private key string for signing. Recommended to use a file instead.  ENV: RSA\n  -k, --sign-key string   RSA private key file path, for signing      ENV: SIGN_KEY\n  -y, --ssl-key  string   path to SSL private key                     ENV: SSL_KEY\n  -t, --ssl-cert string   path to SSL certificate file                ENV: SSL_CERT\n  -a, --addr     string   Server listening Address (default \":6089\")  ENV: ADDR\n  -g, --gen      uint     current token generation. Set to 0 to disable (default 1) ENV: GEN\n  -j, --json              output logs in JSON formt                   ENV: JSON\n  -c, --config   string   Config file to read values from             ENV: CONFIG\n  -d, --conf-dir string   Search this directory for config files      ENV: CONF_DIR\n```\n\n\nThe required parameters (via any method above) are: **pass**, **salt**, and an RSA key, _either_ through **rsa** or **sign-key**. \n\nThe key names for these properties match their `--long` flag names. I.e. `--pass` will be `pass: x`\n\nExample full config file:\n\n```yaml\npass: y0urT0kenEncr7ptn\nsalt: aRandomSaltValue\nsign-key: /path/to/your/signing/rsa.key\nssl-key:  /path/to/SSL/private.key\nssl-cert: /path/to/signed/server.crt\naddr: :443\ngen: 6\njson: true\nauth:\n  password:\n    user1: b6b14ccd83113e4b267e2f0cd150fe2c53f35ae07dcfcdd1d49f4acb30ea681d.a877f3f295643388d873fe378338b9f4\n    user: f082b78b194d2d2487bf0bc351a6eda4dacd244f0b51e27136b7d0e97ee24f44.59d52b113725090d812c2dcbaf6e4cb4\n\n```\n\n`uua -c conf.yaml` will start the server with these parameters. And `user1` and `user2` are the only valid users, identified by their respective passwords (which we don't know)\n\nSet Authentication\n---------------------\n\nRight now the only authentication method is username:password combo. More methods are planned to be added in the future.\n\n### Passwords\n\nUsers are created by adding an entry to the config file (examples below). Passwords are _never_ stored. Only the [Argon2](https://en.wikipedia.org/wiki/Argon2) hash, and salt. The user's password cannot be recovered from this information. Generating the hash and salt to insert into the config file can be done with the included `mkpass` utility. run `./bin/mkpass` and it will prompt for a password. Hit `Enter` and a `hash.salt` will be printed out. This can then be entered into the conf file with the desired username.\n\nExamples:  \nyaml\n```yaml\n# YAML ...\nauth:\n    password:\n        alice: 5911c1e671a5c66d2335d2a704b9844ad3376adcca8e2de194e161e5fbf283ee.adae8cdd7ea456dad56483ce3303ce14\n        bob: 9edd51a088332778885b8743be3859bd847bf5399978717988e437380ec5e315.a6e95c4049c218cae9e047428d526872\n```\njson\n```json\n{\n  \"auth\": {\n    \"password\": {\n      \"alice\":\"5911c1e671a5c66d2335d2a704b9844ad3376adcca8e2de194e161e5fbf283ee.adae8cdd7ea456dad56483ce3303ce14\",\n      \"bob\":\"9edd51a088332778885b8743be3859bd847bf5399978717988e437380ec5e315.a6e95c4049c218cae9e047428d526872\"\n    }\n  }\n}\n\n```\ntoml\n```toml\n[auth]\n  [auth.password]\n    bob = \"9edd51a088332778885b8743be3859bd847bf5399978717988e437380ec5e315.a6e95c4049c218cae9e047428d526872\"\n    alice = \"5911c1e671a5c66d2335d2a704b9844ad3376adcca8e2de194e161e5fbf283ee.adae8cdd7ea456dad56483ce3303ce14\"\n```\n\n\nAuthenticating Users\n----------------------\n\nThe `uua` server exposes two HTTP endpoints:\n\n### Login\n\n**`POST`  `/api/v1/login`**\n\nexpected body format: **json**\n\n```js\n{\n    \"user\": \"$USERNAME\",\n    \"pass\": \"$PASSWORD\",\n    \"app\": \"$APP_NAME\", //optional\n    \"exp\": 1800         //optional, expire time (in seconds)\n}\n```\nExample:\n\n```sh\ncurl -k -XPOST \\\n    -d '{\"user\":\"bob\", \"pass\":\"carrots\", \"app\": \"calendar\", \"exp\": 1800}' \\\n    https://localhost:6089/api/v1/login\n```\n\n**Responses**:\n\n- Success: `HTTP 200`, `{\"token\": \"64vl....\"}`\n- Failure: `HTTP 401`, `{\"error\":\"invalid login\"}`\n\n### Verify\n\n**`POST`  `/api/v1/verify`**\n\nexpected body: Token text\n\nExample:\n\n```sh\ncurl -k -XPOST \\\n    -d \"64vlykYRouY5seenQ0XGs915WEwaC9UM5bzxitbf5Qb2HGkPTshV6ejErupi6kixNrwOuJjKhy2JivE52t/aWjfw5wHTfPMl6w==.EDYWMTl0i9xvQvoklDlSZfwBG0piSa0j8wmCKIDPtMpLGA1U4+pM68CYnZDFZ5++/ftPi++DOi+6BAEAPj3NlLnmu9c5FxkNbVgo0kzgCcIe5nu+uRVf1KYYI6puFmPvZF+zYpeJ+dq9f1X0PwUQexJFRMAj8qLwXXe2Wp9Dtre3HUkTQYJzHZ1y789JJAcx7RpDqTwEUpd0CqEudlw28BW5k6gME9yS7gtCHrZOqTo91iOFxoA9XFO4JA6HKTuc+KOzQqbTpOsE59gKTa17JBR6WzYR9NEXqpTp+UhpJP303cpv2Y/Z/L2gxAtyoJ5eDkUcQVIR2FaJsPMJbnL9qvVgrtW8rVjEqOHpQNqB4IQbV8XWH9euxmc87TUnIJVjjl4jrF/P8XsnVAYgnhWVfyWizaQU9+U5X5t80drY8T3sVWHZwIbagfr+sTMGzaDHvcLMdn4clExy5FcZG/UE393N/JTlVu8LN8N5xNEM/reDCO8SIufBw7eEUgIKkSeW\" \\\n    https://localhost:6089/api/v1/verify\n```\n\n**Responses**:\n\n- Success: `HTTP 200`, `{\"valid\":true, token\":{\"v\":1,\"u\":\"bob\",\"g\":1,\"a\":\"calendar\",\"e\":1560876042}}`\n- Failure: `HTTP 401`, `{\"valid\":false}`\n\nThe Token\n----------\n\nA token looks like this:\n\n```\nnNSmKIDSZl6tguSjz0buFpC/gowjIN1A6dPwkYoAVoekBVbvxUsGcR818nWDGeYUVaykA3Sr8fM+Pwa\nL/y4m8OrOO/DpQNY+.JmezsGZb/zardmNWMub2tPU/ln2xtjYbhpEWcbzTQZ8EoxLWpcJ0IQGO5hEB1\nFEBz8k4ghKnsETZ0ozfFNoSOQv/yMGCUwtFpdK7KjYpWqxEgi/Kkt198uoXmJNQcm8y5eBkI4/FbbTB\nam0cbYQaSIGI6bjiFZ8Xhem5HzS/oTFWZT/uXzSGe4JGcO8BgoWRIEoXQY4Mcpzcl43Zgt3o+KH/U/Q\narIqNkFgIo2SpQR5qFIxovXkma05I/fOZ6YaxduXvAFQNjrSIImfNguGOb6aTqEr5un1YxMSSc9ojK/\n+vK+UolZSWO6H5QJ42+3fKuwsxkjit7BQ4yq5sGry7Rw==\n```\n\nIt is dot (`.`) separated, like a JWT. The first section is `Base64(AES-256-GCM(JSON(content)))`. Where `content` is currently made up of the fields: \n\n- **Expiration**, time\n- **User**\n- **App**, which application requested the token\n- **Version**, the UUA lib version\n- **Generation**, your app version\n\nThe `User` field may be used to identify your own user in whatever way you see fit. Any string that uniquely identifies a user (email address, UUID, etc).\n\nThe `App` field is optional, and may be used in any way you see fit. It is written on token creation. It may be used to specify which app requested the token, and apps may reject tokens not created by themselves, or allow. It's up to you.\n\nThe second part of the token after the `.` is an RSA signature of the first part. The signature is checked on token validation, and rejected for invalid signatures. Expiration is also strictly checked, and expired tokens rejected.\n\nRevocation\n----------\n\n`uua` tokens can be revoked in any of the following ways. It is not currently possible to revoke a few, or single tokens. You may only revoke all current tokens\n\n- Change the RSA signing key. Changing the signature will make all previous tokens invalid on signature checks. New tokens will validate fine\n- Change the Encryption pass or salt. Either of these will force all previous tokens to fail decryption and therefore be invalid.\n- Increment the `Generation`. By default, tokens are created in generation `1`. By increasing the generation, new tokens will be generation `2` (or whatever you set), and all other generations `\u003c 2` will be invalid. This is an easy way to invalidate tokens without having to change keys. If the floor were set to `10`, then all generations 1 through 9 will be invalid. \"Future\" generations will be valid. So `11+` will validate with a current Gen of `10`. This allows for perhaps selectively revoking tiers of tokens.   \n\n\nLicense\n---------\n\nMIT \n\nCopyright (c) 2019 Dan Panzarella \u003cdan@panzarel.la\u003e\n\nSee [LICENSE](LICENSE) for full license","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpzl%2Fuua","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpzl%2Fuua","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpzl%2Fuua/lists"}