{"id":20829594,"url":"https://github.com/celsiusnarhwal/loctocat","last_synced_at":"2025-05-07T21:44:32.597Z","repository":{"id":63715322,"uuid":"569944942","full_name":"celsiusnarhwal/loctocat","owner":"celsiusnarhwal","description":"Simple yet flexible OAuth2 device flow authentication for Python","archived":false,"fork":false,"pushed_at":"2022-11-25T22:19:24.000Z","size":57,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-05T08:56:45.521Z","etag":null,"topics":["authentication","device-flow","oauth","oauth2","python"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/loctocat","language":"Python","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/celsiusnarhwal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-11-24T01:26:50.000Z","updated_at":"2023-11-23T13:34:46.000Z","dependencies_parsed_at":"2023-01-23T00:15:13.211Z","dependency_job_id":null,"html_url":"https://github.com/celsiusnarhwal/loctocat","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celsiusnarhwal%2Floctocat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celsiusnarhwal%2Floctocat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celsiusnarhwal%2Floctocat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celsiusnarhwal%2Floctocat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/celsiusnarhwal","download_url":"https://codeload.github.com/celsiusnarhwal/loctocat/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252961835,"owners_count":21832190,"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":["authentication","device-flow","oauth","oauth2","python"],"created_at":"2024-11-17T23:21:12.041Z","updated_at":"2025-05-07T21:44:32.537Z","avatar_url":"https://github.com/celsiusnarhwal.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# loctocat\n\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/loctocat?logo=pypi\u0026logoColor=white\u0026style=for-the-badge)](https://pypi.org/project/loctocat)\n[![PyPI](https://img.shields.io/pypi/v/loctocat?color=green\u0026logo=pypi\u0026style=for-the-badge\u0026logoColor=white)](https://pypi.org/project/loctocat)\n[![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/celsiusnarhwal/loctocat?color=orange\u0026include_prereleases\u0026label=latest%20release\u0026logo=github\u0026style=for-the-badge)](https://github.com/celsiusnarhwal/loctocat/releases/latest)\n[![PyPI - License](https://img.shields.io/pypi/l/loctocat?color=03cb98\u0026style=for-the-badge)](https://github.com/celsiusnarhwal/loctocat/blob/HEAD/LICENSE.md)\n\nloctocat brings simple yet flexible OAuth 2.0 device flow authentication to Python. It has built-in asyncio support\nand even predefined authenticators for popular services. Plus, it's fully compliant with\n[RFC 8628](https://tools.ietf.org/html/rfc8628), making it compatible with any OAuth2-supporting service that\n(correctly) implements the standard.\n\n## Installation\n\n```bash\npip install loctocat\n```\n\n## Basic Usage\n\n### The `Authenticator` Class\n\nEvery authentication flow starts with loctocat's `Authenticator` class.\n\n```python\nfrom loctocat import Authenticator\n\nauthenticator = Authenticator(\n    client_id=\"your_client_id\",\n    auth_url=\"https://example.com/oauth2/authorize\",\n    token_url=\"https://example.com/oauth2/token\",\n    scopes=[\"list\", \"of\", \"scopes\"],\n)\n```\n\nIt's pretty simple — just instantiate the class with your client ID, authorization URL (where you'll get your device\nand user codes), token URL (where you'll poll the authorization server for an access token), and a list of any scopes\nyou need.\n\nOnce you've got an `Authenticator`, getting an access token is as simple as:\n\n```python\ntoken = authenticator.authenticate()\n```\n\nWhoa. That was easy.\n\n`Authenticator.authenticate()` will, in order:\n\n1. Obtain device and user codes from the authorization server\n2. Prompt the user to visit the verficiation URL and enter the user code\n3. Poll the authorization server for an access token\n4. Return the access token as a string\n\nHere's an example of using `Authenticator` to authenticate with GitHub:\n\n```python\n# https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#device-flow\n\nfrom loctocat import Authenticator\n\nauthenticator = Authenticator(\n    client_id=\"github_client_id\",  # Replace this with your app's actual client ID, obviously.\n    auth_url=\"https://github.com/login/device/code\",\n    token_url=\"https://github.com/login/oauth/access_token\",\n    scopes=[\"repo\"],  # https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps\n)\n\ntoken = authenticator.authenticate()\n```\n\nLike I said, easy. Unless you're building an asynchronous application, in which case this doesn't work at all, since\n`Authenticator.authenticate()` is a blocking call. Fortunately, loctocat has you covered.\n\n### The `AsyncAuthenticator` Class\n\n`AsyncAuthenticator` is a subclass of `Authenticator` that functions exactly the same except all its methods are\nasynchronous (and therefore must be called with `await`). Here's an example of `AsyncAuthenticator` in action:\n\n```python\nfrom loctocat import AsyncAuthenticator\n\nauthenticator = AsyncAuthenticator(\n    client_id=\"your_client_id\",\n    auth_url=\"https://example.com/oauth2/authorize\",\n    token_url=\"https://example.com/oauth2/token\",\n    scopes=[\"list\", \"of\", \"scopes\"],\n)\n\ntoken = await authenticator.authenticate()\n```\n\nWhoa. That was easy.\n\n`AsyncAuthenticator.authenticate()` will, in or—wait, I'm getting déjà vu.\n\n## Advanced Usage\n\nMaybe `Authenticator.authenticate()` is too simplistic for you. Maybe you'd rather, I don't know,\nhandle the user-facing authentication prompt yourself, or control when loctocat starts polling for access tokens.\nFortunately, locotcat has you covered.\n\n(Keep in mind that `AsyncAuthenticator` is a subclass of `Authenticator` and inherits all of its methods and\nattributes.)\n\n### `Authenticator.ping()`\n\n`Authenticator.ping()` requests device and user codes from the authorization server, returning a `LoctocatAuthInfo`\nobject that looks like this:\n\n```py\nclass LoctocatAuthInfo:\n    device_code: str\n    user_code: str\n    verification_uri: str\n    expires_in: int\n    interval: int\n```\n\nPretty self-explanatory. You can do whatever you want with this information (aside from change it\n— `LoctocatAuthInfo`'s attributes are read-only). For example, you could prompt the user with some custom text\ncontaining the user code and verification URI:\n\n```python\nauth_info = authenticator.ping()\n\nprint(f\"Check it out, yo! This is some epic text telling YOU to go {auth_info.verification_uri} and enter {auth_info.user_code}! Swag!\")\n```\n\n### `Authenticator.poll()`\n\n`Authenticator.poll()` polls the authorization server for an access token, returning it as a string. You **don't**\nneed to pass the `LoctocatAuthInfo` object returned by `Authenticator.ping()` to `Authenticator.poll()` — the\nauthorization info is automatically remembered by `Authenticator`. All you have to do is call the method:\n\n```python\n# Authenticator.ping() must have been called on the Authenticator object already or this will not work.\n\ntoken = authenticator.poll()\n```\n\nWhoa. That was easy.\n\n(Fun fact: `Authenticator.authenticate()` is just a wrapper around `ping()` and `poll()`.)\n\n## Pro Usage\n\nMaybe [Advanced Usage](#advanced-usage) isn't advanced enough for you. Maybe you're working with an authorization\nserver that requires parameters beyond those defined by `Authenticator`. Maybe you want to customize the prompts and\nmessages displayed by `Authenticator.authenticate()` without having to use `Authenticator.ping()` and\n`Authenticator.poll()`. Maybe loctocat has a predefined authenticator for a service you like, and you want to use it.\nUnfortunately, loctocat doesn't have you covered.\n\n...\n\nOkay, loctocat actually does have you covered, but that stuff is the domain of loctocat's unfinished documentation\nsite. Emphasis on unfinished. It's not finished yet.\n\nFortunately, loctocat has you covered. loctocat's a pretty small library and it's public modules and classes are all\nproperly documented in the source code, so you're welcome to learn by example(?) and take a look around.\n\nOr you could wait until I finish the documentation site. I'm not your mother.\n\n## FAQ\n\nMaybe you have questions about loctocat that haven't been answered by the rest of this README. Maybe you just want to see me talk to\nmyself for like, two paragraphs. Fortunately, loctocat has you covered.\n\n### Q: loctowhat now\n\nA: Lock + [Octocat](https://octodex.github.com). loctocat was born out of my need for a Python library that implemented\nOAuth 2.0 device flow authentication for GitHub.\n\n### Q: pretty sure I can do this with requests-oauthlib or [INSERT OAUTH LIBRARY HERE] just fine dude\n\nA: Sure you can. In fact, loctocat uses requests and oauthlib under the hood. So let's leave loctocat behind and\nwrite a function to authenticate with GitHub using requests and oauthlib, together!\n\n```python\nimport time\n\nimport requests\nfrom oauthlib.oauth2 import DeviceClient\n\ndef authenticate_with_github(client_id: str, scopes: list[str]) -\u003e str:\n    auth_url = \"https://github.com/login/device/code\"\n    token_url = \"https://github.com/login/oauth/access_token\"\n    client = DeviceClient(client_id=client_id, scope=scopes)\n    \n    ping_uri = client.prepare_request_uri(auth_url)\n    response = requests.post(ping_uri, headers={\"Accept\": \"application/json\"}).json()\n    \n    poll_uri = client.prepare_request_uri(token_url, code=response[\"device_code\"])\n    while True:\n        response = requests.post(poll_uri, headers={\"Accept\": \"application/json\"}).json()\n        \n        if \"error\" in response:\n            if response[\"error\"] in [\"authorization_pending\", \"slow_down\"]:\n                time.sleep(response[\"interval\"])\n                continue\n            else:\n                raise RuntimeError(response[\"error\"])\n        else:\n            return response[\"access_token\"]\n```\n\nDamn, that plate can boil!\n\nNow let's do the same thing, but with loctocat.\n\n```python\nfrom loctocat.predefined import GitHubAuthenticator\n\ndef authenticate_with_github(client_id: str, scopes: list[str]) -\u003e str:\n    authenticator = GitHubAuthenticator(client_id=client_id, scopes=scopes)\n    return authenticator.authenticate()\n\n# \"Hey, that's cheating!\" Fine, let's do it the hard way.\n\nfrom loctocat import Authenticator\n\ndef authenticate_with_github(client_id: str, scopes: list[str]) -\u003e str:\n    authenticator = Authenticator(\n        client_id=client_id,\n        auth_url=\"https://github.com/login/device/code\",\n        token_url=\"https://github.com/login/oauth/access_token\",\n        scopes=scopes\n    )\n    \n    return authenticator.authenticate()\n```\n\nWhoa. That was easy.\n\nPart of the reason I made loctocat is that no other library capable of doing what loctocat does does it in a way that\ndoesn't SUCK. The first example SUCKS. The second example is AWESOME. Case closed.\n\n\n### Q: loctocat isn't working with [INSERT SERVICE HERE] and I'm FRUSTRATED AAAAGGGGGGHHHHH\n\nA: loctocat is compliant with the OAuth 2.0 Device Authorization Grant standard so it's probably the service's fault.\nMake sure the service actually does support the device flow and is generally compliant with RFC 8628. If you're sure\nloctocat is the problem, [open an issue](https://github.com/celsiusnarhwal/loctocat/issues/new).\n\n### Q: oh my god thank you I've been looking for a library like this forever you have no idea\n\nA: You're very welcome. 🙂\n\n## License\n\nIn an age where developers must take great caution not to tread on the intellectual property of others, you must be\nhoping that a such an incredible library is made available under a permissive license. Fortunately,\nloctocat has you covered.\n\nloctocat is licensed under the [MIT License](https://github.com/celsiusnarhwal/loctocat/blob/HEAD/LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcelsiusnarhwal%2Floctocat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcelsiusnarhwal%2Floctocat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcelsiusnarhwal%2Floctocat/lists"}