Delete old/abandoned refresh tokens from user profiles

The proper way to remove a refresh token from the list in the user profile is to actively log out from the device, for which this token was created in the first place. But of course nobody does that and so these refresh tokens become an ever growing list of old & abandoned entries over time, including devices that even may not exist anymore to log out from them the proper way.

Currently there doesn’t seem to be an elegant way in HA to server-side cleanup this list apart from deleting expired entries one by one by clicking on the trashcan icon for every user.

So if you are looking for a faster way to get rid of old entries in this list, you may install jq on your HA server (or copy the HA auth file somewhere else, where you have jq installed, modify it and copy the result back) and use the following one liner:

days=7 authfile=<path-to-ha-config-dir>/.storage/auth tmp=$(tempfile) jq --arg s "$(date -d "-$days days" +"%Y-%m-%dT%H:%M")" 'del( .data.refresh_tokens[] | select(.last_used_at < $s) )' $authfile >$tmp && cp $tmp $authfile && rm $tmp

days defines the number of days since a refresh token was last used and authfile is the path to the HA auth file. In the example above all refresh tokens for all users of your HA instance last used more than 7 days ago will be deleted.

Please note, that I’m not sure, if HA is holding the auth file in RAM and is re-reading or updating it sometimes while running. To make sure these changes come into effect, you should restart HA after deleting the tokens or, to play it safe, delete the tokens only while HA is stopped.

If you have jq installed on your HA server, you may also use this one liner in a cron job or you may define a shell_command for it, thus you can trigger the cleanup from the frontend.

Edit: The one liner above temporarily writes the new auth file to the global temp dir of your HA server. If everything is working, it gets deleted immediately after being copied back. But that may not always be the case and there is of course a risk of this file being left in the temp folder.Thus, if you have security concerns writing this file to a “public” folder on your server, you may set the tmp variable to a location with proper user access restrictions.

5 Likes

I had a pain in the neck dealing with this one liner so I broke it up. Some Linux issue with dates. I run this on my docker instance. Writing this in case someone else has a problem.

tokenclean.sh

#!/bin/bash

days=7
authfile="$PWD/.storage/auth"
tmp=$(mktemp /tmp/abc-script.XXXXXX)
jq --arg s "$(date -d "-$days days" +"%Y-%m-%dT%H:%M")" 'del( .data.refresh_tokens[] | select(.last_used_at < $s) )' $authfile >$tmp && cp $tmp $authfile && rm $tmp
2 Likes

This line is not working in latest HAOS. Get error:

date: invalid date '-7 days'

Found something that this

"$(date -d "-$days days" +"%Y-%m-%dT%H:%M")"

must be

"$(date +"%Y_%m_%d" --date="7 days ago")"

Don’t know if this is correct.

I updated the script to work with the latest Home assistant version (running inside docker) and also fixed it so that it would not delete long lived access tokens

#!/bin/bash

days=7
authfile="$PWD/.storage/auth"
tmp=$(mktemp /tmp/abc-script.XXXXXX)
jq --arg s "$(date -d "-$(( 24*days )):00:00" +"%Y-%m-%dT%H:%M")" 'del( .data.refresh_tokens[] | select(.last_used_at < $s) | select(.token_type == "normal" ) )' $authfile >$tmp && cp $tmp $authfile && rm $tmp
2 Likes

Hi,
I’m a newbie with Home Assistant.
I can’t even find the log out button.
And looks like this topic is still relevant as my access token list is getting out of control.

Is automatic cleanup on the roadmap?
Or is there an addon for this nowadays?

100-200 required clicks a year is getting old, and I’d rather avoid dealing with manually scripting TBH. Maybe there should be some simple sanity check added to HA for this… like just delete refresh tokens after they get to X weeks without being used? I’m a total newb when it comes to auth/encryption matters. So not sure if that would bring unwanted bugs for others, but seems like that would work fine on my end anyways.

Could be a stupid question but how could i integrate this script in my environment using Home Assistant OS ( core-2022.5.4) on rpi4 ?

Hi ,

Same here , how I can integrate this in an automation ?

Thank you

As stated in the beginning, if you create a command line service entry for the command, you can then call that service from an automation. From my recent experience with the script however, the auth service lazy loads and lazy saves, so running the script after a login would most likely not keep the cleanup. Your best bet would be to run it on startup as no auth should have happened at that point. YMMV.

1 Like

Do you have an example on how to integrate this in an automation ?

Thanks…

None of the scripts above worked for me on Home Assistant OS. The problem is with the Busybox ‘date’ command which is more rudimentary and doesn’t support these flags or datetime formats. So I made a version of the script that should work on Busybox.

However, like stated earlier in this thread, just running the script while Home Assistant is running will not work, as Home Assistant keeps a list of these tokens in memory and writes it back to disk when it changes. So the cleanup needs to be done at startup (?). Maybe even before Home Assistant boots?

I tried this and it seemed to work for me. But then I ran into some memory issue with HA. In order to debug that I disabled this script so I haven’t fully gotten to debug this! I still have the memory issue going on, it seems to be caused by the Visual Studio Code Server add-on.

Please comment if this works for you or not!

Paste the following code into /share/cleanAuthTokens.sh

#!/bin/bash
days=7
authfile="/config/.storage/auth"
tmp=$(mktemp /tmp/abc-script.XXXXXX)
((keepafterunixtime=`date +%s` - $days * 24 * 60 * 60))
keepafterisotime=`date -d @$keepafterunixtime -R`
jq --arg s "$keepafterisotime" 'del( .data.refresh_tokens[] | select(.last_used_at < $s) | select(.token_type == "normal" ) )' $authfile >$tmp && cp $tmp $authfile && rm $tmp

Be sure to have more than 1 day in the days variable… Also, the time to retain tokens is not going to be exactly X days ago, it will scrap the hours/minutes/seconds part of the date.

I triggered the script with the built-in shell script integration. Edit your config and add the shell script there.

# Clean access tokens older than X days
shell_command:
  clean_old_auth_tokens: /share/cleanAuthTokens.sh

Then I added an automation that triggers on Home Assistant startup. That seemed to work, but like said I haven’t debugged this enough to be fully sure.

1 Like

Thanks @gdhsa8 for the work’s.
Can i suggest ?

  • include the name of the script in your mktemp command
script_name="${0##*/}" && script_name="${script_name%.*}"
tmp=$(mktemp /tmp/${script_name}.XXXXXX)
  • include a number day (defaut=7) or in the argument if exist (using or not in the shell_command)
days=${1:-7}

Just after using your script, i’ve lost my credential in the companion app :thinking:

Thanks for the great suggestions! I have to try that out later.

Sure, you lose it if it’s old enough. You might want to check the contents of the auth file. Maybe you can modify the jq filter in some way to make it spare that token.

Hi
I dont at ease with jq… so I wander if …

is there a simple way with jq to filter the cleaning phase for short term (1800 ) and preserving the token which are ‘long term’ token ?

Is the following code correct ?:

#!/bin/bash
days=10
cd /usr/share/hassio/homeassistant
authfile="$PWD/.storage/auth"
jq --arg s "$(date -d "-$days days" +"%Y-%m-%dT%H:%M")" 'del(.data.refresh_tokens[] | select(.last_used_at < $s) |  select(.token_type != "long_lived_access_token" and .last_used_at < $s))' $authfile >$authfile.tmp && mv $authfile.tmp $authfile

hello,

i cant create a new long token because my page is full of tokens…

i try your solution but it doesnt work, i am on HASSOS.

i install jq with ssh and terminal addon each time ha start : pip3 install jq

but nothing happened when i lauch the shell command

still a lot and a lot of tokens that slow down the page and cant create a long token anymore…

what i am doing wrong please?

EDIT : i have edit the “auth” file manually : copy my long access token and all data at the beginning, save a new auth, after i have to setup companion app again and Google Assistant again, and all works perfect! i can now create long access token, no more slowdown webpage…my original “auth” file has 58000+ lines!!! for 4 years…lol 3 905Ko!

Now 22Ko, all is good

1 Like

Did you try the script with home-assistant is in a non running state ? ( docker stop homeassistant )

@aladin thanks for your reply,

but sorry i dont understand what you mean…

I am under hassOs, you mean i have to stop HA first with “docker stop homeassistant” ?

Then i dont understand how to use the shell_command to lauch the script if Homeassistant is stoped?

Sorry m’y script has been done in HASSIO context

Hello all

I’m not a programer and i am dyslesic. should this not be a tool found in in HA or a script that can be included via terminal?

1 Like

I didn’t get the above working correctly with HASSOS without tinkering a bit. Here is what I ended up with. The busybox date was not working on my system but the awk workaround did. In HASSOS I did not have to install anything, I just used the terminal to make and run this script.

I would recommend commenting the cp lines at the bottom, and uncommenting the echos before running on your system…

Home Assistant 2023.3.5
Supervisor 2023.04.0
Operating System 9.5
Frontend 20230309.1 - latest

edit: just found an addon that should make this happen automagically. I had no issues getting the auth file cleaned, but then the running of the script at startup and shutdown did not actually remove the tokens. I hope this will help, now installed this TokenRemover Home Assistant add-on:
HomeAssistant/DOCS.md at main · MennovH/HomeAssistant · GitHub

#!/bin/bash

days=14
authfile="/root/config/.storage/auth"

script_name="${0##*/}" && script_name="${script_name%.*}"
tmp=$(mktemp /tmp/${script_name}.XXXXXX)
#echo $tmp

nowunixtime=$(awk 'BEGIN { print systime() }')
keepafterunixtime=$(awk "BEGIN { print systime() - ($days * 24 * 60 * 60) }")
#echo $nowunixtime, $keepafterunixtime

nowisotime=$(date -u --date="@$nowunixtime" +"%Y-%m-%dT%H:%M:%S.123456+00:00")
keepafterisotime=$(date -u --date="@$keepafterunixtime" +"%Y-%m-%dT%H:%M:%S.123456+00:00")
#echo $nowisotime, $keepafterisotime

jq --arg s "$keepafterisotime" '
  del(
    .data.refresh_tokens[] |
    select(.token_type != "long_lived_access_token" and .last_used_at < $s)
  )
' $authfile >$tmp

cp $tmp /tmp/auth.bak 
cp $tmp $authfile
rm $tmp
1 Like