Skip to content

OAuth helpers

deezify.oauth

OAuth 2.0 helpers for the Deezer API.

Deezer supports a server-side flow and a client-side (implicit) flow::

https://developers.deezer.com/api/oauth

Server-side (recommended when you own a web server):

  1. Redirect the user to :func:authorization_url.
  2. Deezer redirects back to your redirect_uri with ?code=....
  3. Exchange the code with :meth:DeezerClient.exchange_code_for_token.

Client-side (browser / mobile apps without a server secret):

  1. Redirect the user to :func:authorization_url with response_type="token".
  2. Deezer redirects back with the token in the URL fragment (#access_token=...&expires=...); parse it with :func:parse_implicit_callback_fragment.

authorization_url(app_id, redirect_uri, perms='basic_access', *, response_type=None, state=None)

Build the URL that starts the OAuth login/consent dialog.

Parameters:

Name Type Description Default
app_id str | int

Your Deezer application id.

required
redirect_uri str

Where Deezer redirects the user afterwards. It must live under the domain registered for the application.

required
perms str | Permission | list[str | Permission] | tuple[str | Permission, ...]

Permission names — a comma string, a list, or :class:~deezify.enums.Permission members, e.g. "basic_access,email" or [Permission.EMAIL].

'basic_access'
response_type str | None

Pass "token" for the client-side (implicit) flow, omit it for the server-side (code) flow.

None
state str | None

Opaque CSRF token echoed back by Deezer; always verify it.

None
Source code in deezify/oauth.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def authorization_url(
    app_id: str | int,
    redirect_uri: str,
    perms: str | Permission | list[str | Permission] | tuple[str | Permission, ...] = "basic_access",
    *,
    response_type: str | None = None,
    state: str | None = None,
) -> str:
    """Build the URL that starts the OAuth login/consent dialog.

    Args:
        app_id: Your Deezer application id.
        redirect_uri: Where Deezer redirects the user afterwards. It must
            live under the domain registered for the application.
        perms: Permission names — a comma string, a list, or
            :class:`~deezify.enums.Permission` members, e.g.
            ``"basic_access,email"`` or ``[Permission.EMAIL]``.
        response_type: Pass ``"token"`` for the client-side (implicit) flow,
            omit it for the server-side (code) flow.
        state: Opaque CSRF token echoed back by Deezer; always verify it.
    """
    params: dict[str, str] = {
        "app_id": str(app_id),
        "redirect_uri": redirect_uri,
        "perms": _normalize_perms(perms),
    }
    if response_type:
        params["response_type"] = response_type
    if state:
        params["state"] = state
    return f"{AUTH_URL}?{urlencode(params)}"

implicit_authorization_url(app_id, redirect_uri, perms='basic_access', *, state=None)

Shortcut for :func:authorization_url with response_type="token".

Source code in deezify/oauth.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def implicit_authorization_url(
    app_id: str | int,
    redirect_uri: str,
    perms: str | Permission | list[str | Permission] | tuple[str | Permission, ...] = "basic_access",
    *,
    state: str | None = None,
) -> str:
    """Shortcut for :func:`authorization_url` with ``response_type="token"``."""
    return authorization_url(
        app_id, redirect_uri, perms, response_type="token", state=state
    )

token_exchange_url(app_id, secret, code)

Build the server-side access_token.php URL for a given code.

Source code in deezify/oauth.py
104
105
106
107
108
def token_exchange_url(app_id: str | int, secret: str, code: str) -> str:
    """Build the server-side ``access_token.php`` URL for a given code."""
    return (
        f"{TOKEN_URL}?{urlencode({'app_id': str(app_id), 'secret': secret, 'code': code})}"
    )

parse_token_response(body)

Parse the access_token.php response body.

Without output=json Deezer answers with a query-string body (access_token=...&expires=...); with output=json it answers JSON. This helper accepts both and always returns a dict.

Source code in deezify/oauth.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def parse_token_response(body: str) -> dict:
    """Parse the ``access_token.php`` response body.

    Without ``output=json`` Deezer answers with a query-string body
    (``access_token=...&expires=...``); with ``output=json`` it answers JSON.
    This helper accepts both and always returns a dict.
    """
    text = (body or "").strip()
    if not text:
        return {}
    if text.startswith("{"):
        import json

        try:
            data = json.loads(text)
        except ValueError:
            return {}
        return data if isinstance(data, dict) else {}
    if "=" not in text:
        return {"access_token": text}
    parsed = dict(parse_qsl(text, keep_blank_values=True))
    if "access_token" not in parsed and text:
        parsed.setdefault("access_token", text)
    return parsed

parse_implicit_callback_fragment(fragment)

Parse the #access_token=...&expires=... fragment of the client-side flow redirect into a dict.

Source code in deezify/oauth.py
137
138
139
140
141
def parse_implicit_callback_fragment(fragment: str) -> dict:
    """Parse the ``#access_token=...&expires=...`` fragment of the
    client-side flow redirect into a dict."""
    fragment = (fragment or "").lstrip("#")
    return dict(parse_qsl(fragment, keep_blank_values=True))