# Keep users signed in

Canonical page: https://docs.ovok.com/python-sdk/guides/keep-users-signed-in

A client built without `client_secret` acts for a signed-in user. It keeps the session in
`storage` and refreshes the access token a minute before it expires.

## Persist the session [#persist-the-session]

The default `MemoryStorage` forgets the session when the process exits. Pass any object with
`get_item`, `set_item` and `remove_item` (the `OvokStorage` protocol) to keep it:

```python
import json
from pathlib import Path

from ovok_sdk import Ovok


class FileStorage:
    def __init__(self, path: Path) -> None:
        self._path = path

    def _read(self) -> dict[str, str]:
        return json.loads(self._path.read_text()) if self._path.exists() else {}

    def get_item(self, key: str) -> str | None:
        return self._read().get(key)

    def set_item(self, key: str, value: str) -> None:
        self._path.write_text(json.dumps({**self._read(), key: value}))

    def remove_item(self, key: str) -> None:
        items = self._read()
        items.pop(key, None)
        self._path.write_text(json.dumps(items))


ovok = Ovok(
    "https://api.sandbox.ovok.com",
    "your-tenant",
    storage=FileStorage(Path("ovok-session.json")),
)
```

Each tenant has its own key, so one store can serve several clients. The stored session has
the same shape as the TypeScript SDK's.

## React when the session ends [#react-when-the-session-ends]

The user is signed out only when the API refuses the refresh token; a network outage keeps
the session for a later retry. Pass `on_unauthenticated` to send the user back to sign-in:

```python
def signed_out(reason):
    print(f"Signed out ({reason.type}, HTTP {reason.status}); please sign in again.")


ovok = Ovok("https://api.sandbox.ovok.com", "your-tenant", on_unauthenticated=signed_out)
```

`ovok.auth.current()` returns the session, or None when no one is signed in.

See [Authentication](/python-sdk/namespaces/auth) for sign-in, MFA and registration.
