Long-lived access token#16453
Conversation
homeassistant/auth/__init__.py
Outdated
| 'refresh tokens') | ||
|
|
||
| if (token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN and | ||
| (client_id is None or client_id.lower().startswith( |
There was a problem hiding this comment.
I think instead we should just enforce client_id is None
| raise ValueError('Client_id is not allowed start with http:// or ' | ||
| 'https:// for long-lived access token') | ||
|
|
||
| if token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN: |
There was a problem hiding this comment.
I think that this is a weird check. We shouldn't enforce this but instead leave this up to the user.
There was a problem hiding this comment.
Current is
1 client_id : 1 refresh_token : 1 long-lived access_token
Did you want to change to
1 client_id : m refresh_token : m long-lived access_token
homeassistant/auth/auth_store.py
Outdated
| client_name: Optional[str] = None, | ||
| client_icon: Optional[str] = None, | ||
| token_type: str = models.TOKEN_TYPE_NORMAL, | ||
| access_token_expiration: Optional[timedelta] = None) \ |
There was a problem hiding this comment.
Isn't this one always passed in and so doesn't have to be optional? I think it's a security concern if expiration defaults are applied whenever we load a refresh token.
There was a problem hiding this comment.
Current workflow, the normal login flow, did not pass in access_token_expiration
Default value will be set in the lowest level of access_token creation. if user pass in None, this field would not be set in **kwargs, therefore the access_token will carry on default expiration value, 30 minutes
There was a problem hiding this comment.
This should not live in auth_store but in auth_manager, as that's where the business logic should live.
homeassistant/auth/auth_store.py
Outdated
| return found | ||
|
|
||
| @callback | ||
| def async_mutate_refresh_token( |
There was a problem hiding this comment.
I am thinking now if all this hassle is even necessary.
If we just never tell the user the refresh token, they can never get a second access token. If our API endpoint just returns the access token, this is no longer our concern.
And if it's not our concern, we don't have to enforce single access token, we don't have to re-generate JWT tokens. It makes everything a lot simpler. We can still keep type, as it helps showing the user what's going on.
There was a problem hiding this comment.
We only use refresh_token as a container to save client information in this case. It never returns back to user. But I am imaging there is "refresh" or "regenerate" button in UI to allow user get a new access token, at the same time revoke previous issued
Back to the above comment in, if we have 1:m for client_id and refresh_token, then this enforce is unnecessary, otherwise, we still need a method to either recreate refresh_token or mutate jwt_key
There was a problem hiding this comment.
I think it's easier to solve the regenerate case by removing old refresh token and generate a new refresh and access token with same client id/icon. auth_store is already growing a lot and this should be kept as simple as possible.
There was a problem hiding this comment.
So I suggest we do without this method.
homeassistant/auth/auth_store.py
Outdated
| from homeassistant.util import dt as dt_util | ||
|
|
||
| from . import models | ||
| from . import models, util |
a48df39 to
463b6ed
Compare
homeassistant/auth/__init__.py
Outdated
| 'refresh tokens') | ||
|
|
||
| if (token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN and | ||
| (client_id is None or client_name is None)): |
There was a problem hiding this comment.
client_id should be None. Client ID is what is used when a user generates a token via an external client. When creating a long lived token, they use the UI and no external client is involved.
homeassistant/auth/__init__.py
Outdated
| for token in list(user.refresh_tokens.values()): | ||
| if (token.client_id == client_id and token.token_type == | ||
| models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN): | ||
| # Each client_id can only have one long_lived_access_token |
There was a problem hiding this comment.
Client ID should always be None. I think that this check can be removed.
homeassistant/auth/__init__.py
Outdated
| 'System generated users can only have system type ' | ||
| 'refresh tokens') | ||
|
|
||
| if not user.system_generated and \ |
There was a problem hiding this comment.
You can collapse this if-statement in the previous one:
if user.system_generated != (token_type == models.TOKEN_TYPE_SYSTEM):
raise ValueError(…)* Allow create refresh_token with specific access_token_expiration * Add token_type, client_name and client_icon * Add unit test * Add websocket API to create long-lived access token * Allow URL use as client_id for long-lived access token * Remove mutate_refresh_token method * Use client name as id for long_lived_access_token type refresh token * Minor change * Do not allow duplicate client name * Update docstring * Remove unnecessary `list`
|
Hi, Help Me! Is the content sent incorrectly? I have successfully authenticated, but I want a long-term token. ... {"type": "auth_required", "ha_version": "0.77.3"} {"type": "auth_ok", "ha_version": "0.77.3"} Ok, It was all successful before here. THEN (error) SEND RECV {"id": 11, "type": "result", "success": false, "error": {"code": 4, "message": "Unknown command."}} |
|
Please open an issue. Closed PRs should not be used for support or bug reports. |
Description:
Implement long-lived access token back end logic
normal,systemorlong_lived_access_tokenclient_idcan only have onelong_lived_access_tokentype refresh_token, each such refresh_token can only have one active access_token.Send websocket command
auth/long_lived_access_tokenwill create or refresh a long-lived access token for current user. Access token will not be saved in Home Assistant. User need to record the token in secure place.{ "id": 11, "type": "auth/long_lived_access_token", "client_id": "gps_logger", "client_name": "GPS Logger", "client_icon": null, "lifespan": 365 }Result will be a long-lived access token:
{ "id": 11, "type": "result", "success": true, "result": "ABCDEFGH" }To use access token, put it in HTTP Authorization Header, for example
curl -H "Authorization: Bearer ABCDEFGH" http://hassbian.local:8123/apiRelated issue (if applicable): fixes back-end part #15195
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/developers.home-assistant#82
Example entry for
configuration.yaml(if applicable):Checklist:
tox. Your PR cannot be merged unless tests passIf user exposed functionality or configuration variables are added/changed:
If the code does not interact with devices: