API authentication¶
Authentication with the API requires a valid Ubuntu SSO user. This user
must be configured as an admin using the snap-proxy
tool:
snap-proxy add-admin [email protected]
The Snap Store and Snap Store Proxy use macaroons for authentication, which are a kind of bearer token that can be constrained and that can be authorised by third-party services. We strongly recommend using pymacaroons or libmacaroons to work with these tokens.
If you want to understand more about how macaroons work, refer to the original paper.
To login, you must first get a root macaroon from the snap store proxy, then discharge (verify) that macaroon with Ubuntu SSO.
Root Macaroon¶
To get a root macaroon:
Request:
POST /v2/auth/issue-store-admin HTTP/1.1
Host: <store domain>
Accept: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json
...
{
"macaroon": "..."
}
The response is simple and matches this JSON Schema:
{
'type': 'object',
'properties': {
'macaroon': {'type': 'string'},
},
'required': ['macaroon'],
'additionalProperties': False,
}
This is your main authentication token, and should be stored persistently.
Discharge Ubuntu SSO caveat¶
The root macaroon contains a caveat that the user must have a valid Ubuntu SSO account. To prove that is the case, we need to discharge that caveat with Ubuntu SSO.
You need to deserialise this root macaroon, and extract the caveat ID
with the location login.ubuntu.com
. For example, using pymacaroons:
macaroon = pymacaroons.Macaroon.deserialize(root_macaroon)
for caveat in macaroon.caveats:
if caveat.location == 'login.ubuntu.com':
return caveat.caveat_id
Then we need to discharge the caveat with Ubuntu SSO.
Request:
POST /api/v2/tokens/discharge HTTP/1.1
Host: login.ubuntu.com
Accept: application/json
Content-Type: application/json
{
"email": ...,
"password": ...,
"otp": ..., # if user account has 2FA enabled
"caveat_id": ...
}
Response:
HTTP/1.1 200 OK
Content-Type: application/json
...
{
"discharge_macaroon": "<discharge macaroon>",
}
Note
For more detailed responses from Ubuntu SSO, particularly handling invalid credentials and 2FA, see the general Ubuntu SSO documentation for OAuth tokens, which is also used by the macaroon discharge endpoint.
You will need to persist the raw root macaroon and the raw discharge macaroon. Together, these are your authentication.
Request authentication¶
To authenticate a request, you must bind the discharge macaroon to the root macaroon, and send that as your value in an ‘Authorisation’ HTTP header.
For example, with pymacaroons:
root = pymacaroon.Macaroon.deserialize(root_raw)
discharge = pymacaroons.Macaroon.deserialize(discharge_raw)
bound = root.prepare_for_request(discharge)
header = 'Macaroon root="{}", discharge="{}"'.format(root_raw, bound.serialize())
Note
If your discharge macaroon has expired, it will be indicated by
indicated by a 401 status code, and a header:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Macaroon needs_refresh=1
In this case you will need to refresh your discharge macaroon, described below, and retry the request.
Refreshing the discharge macaroon¶
Your discharge macaroon has an expiry, and needs refreshing with Ubuntu SSO periodically.
To do so, simply:
Request:
POST /api/v2/tokens/refresh HTTP/1.1
Host: login.ubuntu.com
Accept: application/json
Content-Type: application/json
{
"discharge_macaroon": "<discharge>"
}
Response:
HTTP/1.1 200 OK
Content-Type: application/json
...
{
"discharge_macaroon": "<new discharge>",
}
Update and store persistently this new discharge macaroon for later use.