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 user@example.com
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 authorized 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 (https://login.ubuntu.com/).
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 deserialize 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 'Authorization' 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.