Ctrl K
ComunidadGuías…

docs/auth.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
[Table of contents](README.md#table-of-contents)

# Authentication and access delegations

## Introduction

In this document, we will cover how to protect the usage of the cozy-stack. When
the cozy-stack receives a request, it checks that the request is authorized, and
if yes, it processes it and answers it.

## What about OAuth2?

OAuth2 is about delegating an access to resources on a server to another party.
It is a framework, not a strictly defined protocol, for organizing the
interactions between these 4 actors:

-   the resource owner, the "user" that can click on buttons
-   the client, the website or application that would like to access the
    resources
-   the authorization server, whose role is limited to give tokens but is
    central in OAuth2 interactions
-   the resources server, the server that controls the resources.

For cozy, both the authorization server and the resources server roles are
played by the cozy-stack. The resource owner is the owner of a cozy instance.
The client can be the cozy-desktop app, cozy-mobile, or many other applications.

OAuth2, and its extensions, is a large world. At its core, there is 2 things:
letting the client get a token issued by the authorization server, and using
this token to access to the resources. OAuth2 describe 4 flows, called grant
types, for the first part:

-   Authorization code
-   Implicit grant type
-   Client credentials grant type
-   Resource owner credentials grant type.

On cozy, only the most typical one is used: authorization code. To start this
flow, the client must have a `client_id` and `client_secret`. The Cozy stack
implements the OAuth2 Dynamic Client Registration Protocol (an extension to
OAuth2) to allow the clients to obtain them.

OAuth2 has also 3 ways to use a token:

-   in the query-string (even if the spec does not recommended it)
-   in the POST body
-   in the HTTP Authorization header.

On cozy, only the HTTP header is supported.

OAuth2 has a lot of assumptions. Let's see some of them and their consequences
on Cozy:

-   TLS is very important to secure the communications. in OAuth 1, there was a
    mechanism to sign the requests. But it was very difficult to get it right
    for the developers and was abandonned in OAuth2, in favor of using TLS. The
    Cozy instance are already accessible only in HTTPS, so there is nothing
    particular to do for that.

-   There is a principle called TOFU, Trust On First Use. It said that if the
    user will give his permission for delegating access to its resources when
    the client will try to access them for the first time. Later, the client
    will be able to keep accessing them even if the user is no longer here to
    give his permissions.

-   The client can't make the assumptions about when its tokens will work. The
    tokens have no meaning for him (like cookies in a browser), they are just
    something it got from the authorization server and can send with its
    request. The access token can expire, the user can revoke them, etc.

-   OAuth 2.0 defines no cryptographic methods. But a developer that want to use
    it will have to put her hands in that.

If you want to learn OAuth 2 in details, I recommend the
[OAuth 2 in Action book](https://www.manning.com/books/oauth-2-in-action).

## The cozy stack as an authorization server

In general, the cozy stack manages the authentication itself. This is what is
described below. In some special cases, an integration with other softwares can
be mandatory: this is possible to configure via
[delegated authentication](./delegated-auth.md).

### GET /auth/login

Display a form with a password field to let the user authenticates herself to
the cozy stack.

This endpoint accepts a `redirect` parameter. If the user is already logged in,
she will be redirected immediately. Else, the parameter will be transfered in
the POST. This parameter can only contain a link to an application installed on
the cozy (thus to a subdomain of the cozy instance). To protect against stealing
authorization code with redirection, the fragment is always overriden:

```http
GET /auth/login?redirect=https://contacts.cozy.example.org/foo?bar#baz HTTP/1.1
Host: cozy.example.org
Cookie: ...
```

**Note**: the redirect parameter should be URL-encoded. We haven't done that to
make it clear what the path (`foo`), the query-string (`bar`), and the fragment
(`baz`) are.

```http
HTTP/1.1 302 Moved Temporarily
Location: https://contacts.cozy.example.org/foo?bar#
```

If the `redirect` parameter is invalid, the response will be `400 Bad Request`.
Same for other parameters, the redirection will happen only on success (even if
OAuth2 says the authorization server can redirect on errors, it's very
complicated to do it safely, and it is better to avoid this trap).

### POST /auth/login

After the user has typed her passphrase and clicked on `Login`, a request is
made to this endpoint.

The `redirect` parameter is passed inside the body. If it is missing, the
redirection will be made against the default target: the home application of
this cozy instance. The redirect can be a full URL (like
`http://cozy-drive.example.org/#/folder`), or just a slug+path+hash (like
`drive/#/folder`).

```http
POST /auth/login HTTP/1.1
Host: cozy.example.org
Content-Type: application/x-www-form-urlencoded

passphrase=p4ssw0rd&redirect=https%3A%2F%2Fcontacts.cozy.example.org
```

```http
HTTP/1.1 302 Moved Temporarily
Set-Cookie: ...
Location: https://contacts.cozy.example.org/foo
```

When two-factor authentication (2FA) authentication is activated, this endpoint
will not directly sent a redirection after this first passphrase step. In such
case, a `200 OK` response is sent along with a token value in the response
(either in JSON if requested or directly in a new HTML form).

Along with this token, a 2FA passcode is sent to the user via another transport
(email for instance, depending on the user's preferences). Another request
should be sent to `/auth/twofactor` with a valid pair `(token, passcode)`,
ensuring that the user correctly entered its passphrase _and_ received a fresh
passcode by another mean.

### POST /auth/twofactor

```http
POST /auth/twofactor HTTP/1.1
Host: cozy.example.org
Content-Type: application/x-www-form-urlencoded

two-factor-token=123123123123&two-factor-passcode=678678&redirect=https%3A%2F%2Fcontacts.cozy.example.org
```

```http
HTTP/1.1 302 Moved Temporarily
Set-Cookie: ...
Location: https://contacts.cozy.example.org/foo
```

### POST /auth/login/flagship

This endpoint is similar to `POST /auth/login`, but it allows the flagship app
to also obtain OAuth access and register tokens without having to make the
OAuth dance (which can be awkward for the user).

#### Request

```http
POST /auth/login/flagship HTTP/1.1
Host: alice.example.com
Content-Type: application/json
```

```json
{
  "passphrase": "4f58133ea0f415424d0a856e0d3d2e0cd28e4358fce7e333cb524729796b2791",
  "client_id": "64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3",
  "client_secret": "eyJpc3Mi[...omitted for brevity...]"
}
```

#### Response

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "access_token": "OWY0MjNjMGEtOTNmNi0xMWVjLWIyZGItN2I5YjgwNmRjYzBiCg",
  "token_type": "bearer",
  "refresh_token": "YTUwMjcyYjgtOTNmNi0xMWVjLWE4YTQtZWJhMzlmMTAwMWJiCg",
  "scope": "*"
}
```

**Note:** if two-factor authentication is enabled on the Cozy, an email
will be sent to the user with a code, and this request will return:

```http
HTTP/1.1 401 Unauthorized
Content-Type: application/json
```

```json
{
  "two_factor_token": "123123123123"
}
```

Then, the client can retry by sending the two-factor token and code:

```json
{
  "passphrase": "4f58133ea0f415424d0a856e0d3d2e0cd28e4358fce7e333cb524729796b2791",
  "client_id": "64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3",
  "client_secret": "eyJpc3Mi[...omitted for brevity...]",
  "two_factor_token": "123123123123",
  "two_factor_code": "123456"
}
```

**Note:** if the two-factor authentication is enabled, and the cloudery has
already verified the email address, a parameter `email_verified_code` can be sent
to skip another 2FA code sent by mail.


```json
{
  "passphrase": "4f58133ea0f415424d0a856e0d3d2e0cd28e4358fce7e333cb524729796b2791",
  "client_id": "64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3",
  "client_secret": "eyJpc3Mi[...omitted for brevity...]",
  "email_verified_code": "987456321"
}
```

**Note:** if the OAuth client has not been certified as the flagship app,
this request will return:

```http
HTTP/1.1 202 Accepted
Content-Type: application/json
```

```json
{
  "session_code": "ZmY4ODI3NGMtOTY1Yy0xMWVjLThkMDgtMmI5M2"
}
```

The `session_code` can be put in the query string while opening the OAuth
authorize page. It will be used to open the session, and let the user type the
6-digits code they have received by mail to confirm that they want to use this
app as the flagship app.

### DELETE /auth/login

This can be used to log-out the user. An app token must be passed in the
`Authorization` header, to protect against CSRF attack on this (this can part of
bigger attacks like session fixation).

```http
DELETE /auth/login HTTP/1.1
Host: cozy.example.org
Cookie: seesioncookie....
Authorization: Bearer app-token
```

### DELETE /auth/login/others

This can be used to log-out all active sessions except the one used by the
request. This allow to disconnect any other users currenctly authenticated on
the system. An app token must be passed in the `Authorization` header, to
protect against CSRF attack on this (this can part of bigger attacks like
session fixation).

```http
DELETE /auth/login/others HTTP/1.1
Host: cozy.example.org
Cookie: seesioncookie....
Authorization: Bearer app-token
```

### POST /auth/magic_link

If the authentication via magic link is enabled on this instance, this endpoint
will send an email to the user with a magic link. If the user clicks on this
link, they will be authenticated on the Cozy.

### GET /auth/magic_link?code=...

When the user has received an email with a magic link, the link goes to the
endpoint, where the user will be allowed to enter the Cozy.

### POST /auth/magic_link/twofactor

When two-factor authentication is enabled on a Cozy, this endpoint can be used
to create a session.

### POST /auth/magic_link/flagship

This endpoint allows the flagship app to also obtain OAuth access and register
tokens without having to make the OAuth dance (which can be awkward for the
user). It requires a code sent by email to the user.

#### Request

```http
POST /auth/magic_link/flagship HTTP/1.1
Host: alice.example.com
Content-Type: application/json
```

```json
{
  "magic_code": "ODFhNzkxYTAtYjZiYi0wMTNiLTE1YzQtMThjMDRkYWJhMzI2",
  "client_id": "64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3",
  "client_secret": "eyJpc3Mi[...omitted for brevity...]"
}
```

#### Response

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "access_token": "OWY0MjNjMGEtOTNmNi0xMWVjLWIyZGItN2I5YjgwNmRjYzBiCg",
  "token_type": "bearer",
  "refresh_token": "YTUwMjcyYjgtOTNmNi0xMWVjLWE4YTQtZWJhMzlmMTAwMWJiCg",
  "scope": "*"
}
```

**Note:** if two-factor authentication is enabled on the Cozy, an email
will be sent to the user with a code, and this request will return:

```http
HTTP/1.1 401 Unauthorized
Content-Type: application/json
```

```json
{
  "error": "passphrase is required as second authentication factor"
}
```

Then, the client can retry by sending the two-factor token and code:

```json
{
  "magic_code": "ODFhNzkxYTAtYjZiYi0wMTNiLTE1YzQtMThjMDRkYWJhMzI2",
  "client_id": "64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3",
  "client_secret": "eyJpc3Mi[...omitted for brevity...]",
  "passphrase": "4f58133ea0f415424d0a856e0d3d2e0cd28e4358fce7e333cb524729796b2791"
}
```

**Note:** if the OAuth client has not been certified as the flagship app,
this request will return:

```http
HTTP/1.1 202 Accepted
Content-Type: application/json
```

```json
{
  "session_code": "ZmY4ODI3NGMtOTY1Yy0xMWVjLThkMDgtMmI5M2"
}
```

The `session_code` can be put in the query string while opening the OAuth
authorize page. It will be used to open the session, and let the user type the
6-digits code they have received by mail to confirm that they want to use this
app as the flagship app.

### GET /auth/passphrase_reset

Display a form for the user to reset its password, in case he has forgotten it
for example. If the user is connected, he won't be shown this form and he will
be directly redirected to his cozy.

This endpoint accepts a `hideBackButton` parameter. If this parameter is present
and set to `true` then the passphrase reset page won't display any button to go
back to the login page.
This is useful when this page is opened in a different context from the one in
which the login page was opened (e.g. a browser vs a mobile native application).

It is also possible to use `from=settings` parameter in the query-string, to go
back to the settings app after the password has been reset. It is useful when
the user wants to change their email address, as the process for changing this
address requires the password.

```http
GET /auth/passphrase_reset?hideBackButton=true HTTP/1.1
Host: cozy.example.org
Content-Type: text/html
Cookie: ...
```

### POST /auth/hint

Send the password hint by email.

```http
POST /auth/hint HTTP/1.1
Host: cozy.example.org
```

### POST /auth/onboarding/resend

Resend the activation link by email to finalize the onboarding.

```http
POST /auth/onboarding/resend HTTP/1.1
Host: cozy.example.org
```

### POST /auth/passphrase_reset

After the user has clicked on the reset button of the passphrase reset form, it
will execute a request to this endpoint.

This endpoint will create a token for the user to actually renew his passphrase.
The token has a short-live duration of about 15 minutes. After the token is
created, it is sent to the user on its mailbox.

This endpoint will redirect the user on the login form page.

```http
POST /auth/passphrase_reset HTTP/1.1
Host: cozy.example.org
Content-Type: application/x-www-form-urlencoded

csrf_token=123456890
```

### GET /auth/passphrase_renew

Display a form for the user to enter a new password. This endpoint should be
used with a `token` query parameter. This token makes sure that the user has
actually reset its passphrase and should have been sent via its mailbox.

```http
GET /auth/passphrase_renew?token=123456789 HTTP/1.1
Host: cozy.example.org
Content-Type: text/html
Cookie: ...
```

### POST /auth/passphrase_renew

After the user has entered its new passphrase in the renew passphrase form, a
request is made to this endpoint to renew the passphrase.

This endpoint requires a valid token to actually work. In case of a success, the
user is redirected to the login form.

```http
POST /auth/passphrase_reset HTTP/1.1
Host: cozy.example.org
Content-Type: application/x-www-form-urlencoded

csrf_token=123456890&passphrase_reset_token=123456789&passphrase=mynewpassphrase
```

#### Parameters

| Form parameter         | Description                               |
| ---------------------- | ----------------------------------------- |
| passphrase_reset_token | the token to authenticate the request     |
| csrf_token             | the token to protect against CSRF attacks |
| passphrase             | the new password                          |
| hint                   | the hint to find again the password       |
| iterations             | the number of PBKDF2 iterations           |
| key                    | the encrypted master key for bitwarden    |
| public_key             | the public key for the cozy organization  |
| private_key            | the private key for the cozy organization |

### GET /auth/passphrase

This page renders a form to set the password for an onboarding instance. This
endpoint expects a valid `registerToken` parameter.

In case of success, the instance is marked as onboarded and the user is
redirected to his home.

If the instance is already onboarded, the user is redirected to his home.

It is also possible to give a `redirection` parameter to redirect the user to
another application.

```http
GET /auth/passphrase/?registerToken=e0fbe2c5b90cdcdd9b3487b48b480e0b&redirection=drive/%23/files HTTP/1.1
Host: cozy.example.org
Content-Type: text/html
```

### GET /auth/confirm

An application can ask the user to re-authenticate them-selves before making an
important action (like erasing a pin code). To do that, the application will
sent the user to this page where the stack will show a form.

Two parameters in the query string can be sent:

- `state` (mandatory), which can be seen as an identifier for the confirmation
- `redirect` (optional), where the user will be redirected after the confirmation.

```http
GET /auth/confirm?state=51814f30-5818-0139-9348-543d7eb8149c&redirect=http://banks.cozy.localhost:8080/ HTTP/1.1
```

The application can know the user has confirmed their identity by subscribing
to a real-time event or by looking at the URL after the redirection. The URL
must contain the state given by the app, and a code that can be checked by
calling `POST /auth/confirm/code` (see below).

### POST /auth/confirm

Send the hashed password for confirming the authentication.

#### Redirection

```http
HTTP/1.1 302 Moved Temporarily
Location: http://banks.cozy.localhost:8080/?state=51814f30-5818-0139-9348-543d7eb8149c&code=543d7eb8149c
```

#### Real-time via websockets

If it succeeds, a real-time event will be sent:

```
client > {"method": "AUTH",
          "payload": "xxAppOrAuthTokenxx="}
client > {"method": "SUBSCRIBE",
          "payload": {"type": "io.cozy.auth.confirmations"}}
server > {"event": "CREATED",
          "payload": {"id": "51814f30-5818-0139-9348-543d7eb8149c",
                      "type": "io.cozy.auth.confirmations"}}
```

### GET /auth/confirm/:code

Send the code from the URL to check that the user has really confirmed their
identity (and not just typed the URL them-self).

```http
GET /auth/confirm/543d7eb8149c HTTP/1.1
```

The response will be a 204 No Content if the code is valid (and a 401 else).

### POST /auth/register

This route is used by OAuth2 clients to dynamically register them-selves.

See
[OAuth 2.0 Dynamic Client Registration Protocol](https://tools.ietf.org/html/rfc7591)
for the details.

The client must send a JSON request, with at least:

-   `redirect_uris`, an array of strings with the redirect URIs that the client
    will use in the authorization flow
-   `client_name`, human-readable string name of the client to be presented to
    the end-user during authorization
-   `software_id`, an identifier of the software used by the client (it should
    remain the same for all instances of the client software, whereas
    `client_id` varies between instances).

It can also send the optional fields:

-   `client_kind` (possible values: web, desktop, mobile, browser, etc.)
-   `client_uri`, URL string of a web page providing information about the
    client
-   `logo_uri`, to display an icon to the user in the authorization flow
-   `policy_uri`, URL string that points to a human-readable privacy policy
    document that describes how the deployment organization collects, uses,
    retains, and discloses personal data
-   `software_version`, a version identifier string for the client software.
-   `notification_platform`, to activate notifications on the associated device,
    this field specify the platform used to send notifications:
    - `"android"`: for Android devices with notifications via Firebase Cloud
      Messaging
    - `"ios"`: for iOS devices with notifications via Firebase Cloud
      Messaging or APNS/2
    - `"huawei"`: for huawei devices with Push Kit
-   `notification_device_token`, the token used to identify the mobile device
    for notifications.

The server gives to the client the previous fields and these informations:

-   `client_id`
-   `client_secret`
-   `registration_access_token`

Example:

```http
POST /auth/register HTTP/1.1
Host: cozy.example.org
Content-Type: application/json
Accept: application/json
```

```json
{
    "redirect_uris": ["https://client.example.org/oauth/callback"],
    "client_name": "Client",
    "software_id": "github.com/example/client",
    "software_version": "2.0.1",
    "client_kind": "web",
    "client_uri": "https://client.example.org/",
    "logo_uri": "https://client.example.org/logo.svg",
    "policy_uri": "https://client/example.org/policy"
}
```

```http
HTTP/1.1 201 Created
Content-Type: application/json
```

```json
{
    "client_id": "64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3",
    "client_secret": "eyJpc3Mi[...omitted for brevity...]",
    "client_secret_expires_at": 0,
    "registration_access_token": "J9l-ZhwP[...omitted for brevity...]",
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "redirect_uris": ["https://client.example.org/oauth/callback"],
    "client_name": "Client",
    "software_id": "github.com/example/client",
    "software_version": "2.0.1",
    "client_kind": "web",
    "client_uri": "https://client.example.org/",
    "logo_uri": "https://client.example.org/logo.svg",
    "policy_uri": "https://client/example.org/policy"
}
```


### POST /auth/registerFromWebApp

This route is similar to `POST /auth/register` but is restricted to the Settings
application only. It uses the same OAuth2 Dynamic Client Registration Protocol
and accepts the same request/response format as `POST /auth/register`.

**Important:** This endpoint requires authentication from the Settings webapp. The
request must include a valid token from the Settings application, otherwise it
will return a 403 Forbidden error.

Example:

```http
POST /auth/registerFromWebApp HTTP/1.1
Host: cozy.example.org
Content-Type: application/json
Accept: application/json
Authorization: Bearer <settings-app-token>
```

The request and response format are identical to `POST /auth/register`.


#### Linked applications

Some OAuth applications are a mobile or desktop version of a Cozy webapp. For
them, it's possible to use a special `software_id` to make a link between the
mobile/desktop application and the webapp. The `software_id` must be in a
`registry://slug` format, like `registry://drive` for Cozy-Drive for example.
When it is the case, the mobile/desktop will share its permissions with the
webapp. It means several things:

- The mobile/desktop app will have the same permissions that the linked webapp.
- When a sharing by link is done in the mobile/desktop application, the linked
  webapp will be able to revoke it later, and vice versa.
- When the user accepts to give access to its Cozy to the mobile/desktop app,
  the linked webapp will be installed on the Cozy if it was not already the
  case.
- When the linked webapp is uninstalled, the right to access the Cozy for the
  mobile/desktop app will be revoked.

### GET /auth/register/:client-id

This route is used by the clients to get informations about them-selves. The
client has to send its registration access token to be able to use this
endpoint.

See
[OAuth 2.0 Dynamic Client Registration Management Protocol](https://tools.ietf.org/html/rfc7592)
for more details.

```http
GET /auth/register/64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3 HTTP/1.1
Host: cozy.example.org
Accept: application/json
Authorization: Bearer J9l-ZhwP...
```

```http
HTTP/1.1 201 Created
Content-Type: application/json
```

```json
{
    "client_id": "64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3",
    "client_secret": "eyJpc3Mi[...omitted for brevity...]",
    "client_secret_expires_at": 0,
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "redirect_uris": ["https://client.example.org/oauth/callback"],
    "client_name": "Client",
    "software_id": "github.com/example/client",
    "software_version": "2.0.1",
    "client_kind": "web",
    "client_uri": "https://client.example.org/",
    "logo_uri": "https://client.example.org/logo.svg",
    "policy_uri": "https://client/example.org/policy"
}
```

### PUT /auth/register/:client-id

This route is used by the clients to update informations about them-selves. The
client has to send its registration access token to be able to use this
endpoint.

**Note:** the client can ask to change its `client_secret`. To do that, it must
send the current `client_secret`, and the server will respond with the new
`client_secret`.

```http
PUT /auth/register/64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3 HTTP/1.1
Host: cozy.example.org
Accept: application/json
Content-Type: application/json
Authorization: Bearer J9l-ZhwP...
```

```json
{
    "client_id": "64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3",
    "client_secret": "eyJpc3Mi[...omitted for brevity...]",
    "redirect_uris": ["https://client.example.org/oauth/callback"],
    "client_name": "Client",
    "software_id": "github.com/example/client",
    "software_version": "2.0.2",
    "client_kind": "web",
    "client_uri": "https://client.example.org/",
    "logo_uri": "https://client.example.org/client-logo.svg",
    "policy_uri": "https://client/example.org/policy",
    "notification_platform": "android",
    "notification_device_token": "XXXXxxxx..."
}
```

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
    "client_id": "64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3",
    "client_secret": "IFais2Ah[...omitted for brevity...]",
    "client_secret_expires_at": 0,
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "redirect_uris": ["https://client.example.org/oauth/callback"],
    "client_name": "Client",
    "software_id": "github.com/example/client",
    "software_version": "2.0.2",
    "client_kind": "web",
    "client_uri": "https://client.example.org/",
    "logo_uri": "https://client.example.org/client-logo.svg",
    "policy_uri": "https://client/example.org/policy"
}
```

### DELETE /auth/register/:client-id

This route is used by the clients to unregister them-selves. The client has to
send its registration access token to be able to use this endpoint.

```http
DELETE /auth/register/64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3 HTTP/1.1
Host: cozy.example.org
Authorization: Bearer J9l-ZhwP...
```

```http
HTTP/1.1 204 No Content
```

### POST /auth/clients/:client-id/challenge

This route can be used to start the process for certifying that an app is
really what it tells to be by using the android/iOS APIs
(PlayIntegrity/SafetyNet/AppleAttestation). It returns a nonce that must be
used in the certificate.

The client must send its registration access token to use this endpoint.

```http
POST /auth/clients/64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3/challenge HTTP/1.1
Host: cozy.example.org
Authorization: Bearer J9l-ZhwP...
```

```http
HTTP/1.1 201 Created
Content-Type: application/json
```

```json
{
  "nonce": "MmE3OTM1ZDItNWY0ZC0xMWVjLTg3NT"
}
```

### POST /auth/clients/:client-id/attestation

This route can be used to finish the process for certifying that an app is
really what it tells to be by using the android/iOS APIs. The client can send
its attestation.

```http
POST /auth/clients/64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3/attestation HTTP/1.1
Host: cozy.example.org
Content-Type: application/json
```

```json
{
  "platform": "android",
  "challenge": "MmE3OTM1ZDItNWY0ZC0xMWVjLTg3NT",
  "attestation": "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}
```

Note: the `platform` parameter can be `"android"` or `"ios"`. For `ios`, a
`"keyId"` parameter is also required. For `android`, the `"issuer"` can be
`"playintegrity"` to use the Play Integrity API instead of the SafetyNet API.

```http
HTTP/1.1 204 No Content
```

### POST /auth/clients/:client-id/flagship

This route can be used to send a 6-digits code to manually certify a client as
belonging to the flagship app.

```http
POST /auth/clients/64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3/flagship HTTP/1.1
Host: cozy.example.org
Content-Type: application/x-www-form-urlencoded

code=123456&token=123123123123123
```

```http
HTTP/1.1 204 No Content
```

### GET /auth/authorize

When an OAuth2 client wants to get access to the data of the cozy owner, it
starts the OAuth2 dance with this step. The user is shown what the client asks
and has an accept button if she is OK with that.

In case a limit has been set on the Cozy to the number of user-connected OAuth
clients, and this limit has been reached already, the user will be presented a
screen requesting to either remove some existing clients or, if enabled,
increase the limit (e.g. by subscribing to a plan with a greater limit). Once
the number of connected clients is brought back under the limit, the OAuth flow
will resume and the permissions screen will be displayed.

The parameters are:

-   `client_id`, that identify the client
-   `redirect_uri`, it has to be exactly the same as the one used in
    registration
-   `state`, it's a protection against CSRF on the client (a random string
    generated by the client, that it can check when the user will be redirected
    with the authorization code. It can be used as a key in local storage for
    storing a state in a SPA).
-   `response_type`, only `code` is supported
-   `scope`, a space separated list of the [permissions](permissions.md) asked
    (like `io.cozy.files:GET` for read-only access to files).

```http
GET /auth/authorize?client_id=oauth-client-1&response_type=code&scope=io.cozy.files%3AGET%20io.cozy.contacts&state=Eh6ahshepei5Oojo&redirect_uri=https%3A%2F%2Fclient.org%2F HTTP/1.1
Host: cozy.example.org
```

**Note** we warn the user that he is about to share his data with an application
which only the callback URI is guaranteed.

#### PKCE extension

To improve security, the client can use the [PKCE for OAuth
2](https://oauth.net/2/pkce/). In that case, two additional parameters must be
send to `GET /auth/authorize`:

- `code_challenge`: the client creates a `code_verifier`, and then derive the
  `code_challenge` from it.
- `code_challenge_method`: it must be `S256` (the only supported method).

As a reminder, the relation between `code_verifier` and `code_challenge` is the
following:

```
code_challenge = BASE64URL-ENCODE(SHA256(code_verifier))
```

And, the `code_verifier` parameter must be sent to `POST /auth/access_token`
(see below).

### POST /auth/authorize

When the user accepts, her browser send a request to this endpoint:

```http
POST /auth/authorize HTTP/1.1
Host: cozy.example.org
Content-Type: application/x-www-form-urlencoded

state=Eh6ahshepei5Oojo&client_id=oauth-client-1&scope=io.cozy.files%3AGET%20io.cozy.contacts&csrf_token=johw6Sho&response_type=code&redirect_uri=https%3A%2F%2Fclient.org%2F
```

**Note**: this endpoint is protected against CSRF attacks.

The user is then redirected to the original client, with an access code in the
URL:

```http
HTTP/1.1 302 Moved Temporarily
Location: https://client.org/?state=Eh6ahshepei5Oojo&code=Aih7ohth#
```

### GET /auth/authorize/sharing & POST /auth/authorize/sharing

They are similar to `/auth/authorize`: they also make the user accept an OAuth
thing, but it is specialized for sharing. They are a few differences, like the
scope format (sharing rules, not permissions) and the redirection after the
POST (with `sharing=<sharing-id>` in the query string).

### GET /auth/authorize/move & POST /auth/authorize/move

They are similar to `/auth/authorize`, but instead of a page that lists the
permissions, the user will be asked to type their password to confirm the
action (even if they are already logged-in). If the 2FA is activated, the code
will be asked too. This strong action seems adequate for authorizing to erase
the data in the Cozy before importing data from another Cozy.

#### Request GET

```http
GET /auth/authorize/move?state=8d560d60&client_id=oauth-client-2&redirect_uri=https://move.cozycloud.cc/callback/target HTTP/1.1
Server: target.cozy.example
```

#### Response GET

```http
HTTP/1.1 200 OK
Content-Type: application/html
```

#### Request POST

```http
POST /auth/authorize/move HTTP/1.1
Server: target.cozy.example
Content-Type: application/x-www-form-urlencoded

passphrase=hashed&state=8d560d60&client_id=oauth-client-2&csrf_token=johw6Sho&redirect_uri=https%3A%2F%2Fmove.cozycloud.cc%2Fcallback%2Ftarget
```

#### Response POST

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "redirect": "https://move.cozycloud.cc/callback/target?code=543d7eb8149c&used=123456&quota=5000000&state=8d560d60&vault=true"
}
```

### POST /auth/access_token

Now, the client can check that the state is correct, and if it is the case, ask
for an `access_token`. It can use this route with the `code` given above.

This endpoint is also used to refresh the access token, by sending the
`refresh_token` instead of the `code`.

The origins allowed for [`POST /auth/token_exchange`](#post-authtoken_exchange)
get CORS headers on this endpoint, so that an application holding a token from
`token_exchange` can refresh it from its own origin.

The parameters are:

-   `grant_type`, with `authorization_code` or `refresh_token` as value
-   `code` or `refresh_token`, depending on which grant type is used
-   `client_id`
-   `client_secret`

Example:

```http
POST /auth/access_token HTTP/1.1
Host: cozy.example.org
Content-Type: application/x-www-form-urlencoded
Accept: application/json

grant_type=authorization_code&code=Aih7ohth&client_id=oauth-client-1&client_secret=Oung7oi5
```

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "access_token": "ooch1Yei",
  "token_type": "bearer",
  "refresh_token": "ui0Ohch8",
  "scope": "io.cozy.files:GET io.cozy.contacts"
}
```

### POST /auth/token_exchange

This endpoint exchanges an external OIDC `id_token` for a normal Cozy OAuth
client and token pair on the target instance.

It supports two configured exchanges:

- admin applications that authenticate with an external identity provider, then
  need to call Cozy APIs directly on an organization instance
- trusted user applications mapped by OIDC audience to a Cozy linked app, such
  as `twake-mail-web` to `registry://mail`

The target Cozy instance is the request host. The exchanged `id_token` must:

- be signed by the configured OIDC provider
- match the configured issuer and audience
- contain `iat` and `exp` claims
- contain a `sid` claim so the created OAuth client can be revoked by OIDC
  backchannel logout

For admin exchanges, the `id_token` must also:

- contain an `org_id` claim equal to the target instance organization id
- contain an `org_role` claim equal to `owner` or `admin`

For user app exchanges, the `id_token` audience must be present in the
`oidc.app_token_exchange` stack configuration. The token must also match the
target Cozy instance. When the matched app exchange entry sets `instance_claim`,
that claim must contain the target instance domain. Otherwise the existing OIDC
instance mapping is used (`sub` to `oidc_id` when `allow_custom_instance` is
true, otherwise `userinfo_instance_field` with prefix/suffix). The returned
scope is derived from the configured linked app manifest, for example
`@io.cozy.apps/mail`; caller-provided scopes are rejected.

The request body is JSON:

- `id_token`, the external OIDC token
- `exchange_type`, optional, either `admin` or `app`; when omitted, `admin`
  is used
- `scope`, for admin exchanges only, a space-separated list of allowed
  doctypes: `io.cozy.files`, `io.cozy.contacts`, `io.cozy.contacts.groups`

Example:

```http
POST /auth/token_exchange HTTP/1.1
Host: myorg123.example.com
Content-Type: application/json
Accept: application/json

{
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InRva2VuLWV4Y2hhbmdlIn0...",
  "scope": "io.cozy.files"
}
```

Example app exchange:

```json
{
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InRva2VuLWV4Y2hhbmdlIn0...",
  "exchange_type": "app"
}
```

Response:

```http
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
```

```json
{
  "access_token": "eyJhbGciOiJS",
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOiJS",
  "scope": "io.cozy.files",
  "client_id": "64ce5cb0-bd4c-11e6-880e-b3b7dfda89d3",
  "client_secret": "Oung7oi5",
  "registration_access_token": "reg123"
}
```

The returned OAuth client is a normal Cozy OAuth client:

- `client_id`, `client_secret`, `access_token`, and `refresh_token` can be
  used directly with `cozy-client`
- `registration_access_token` can be used with
  `DELETE /auth/register/:client-id` to revoke that exchanged client

The created OAuth client is bound to the upstream OIDC session so it can be
revoked by OIDC backchannel logout.

Repeated exchanges reuse an existing client when the OIDC provider/context,
instance, session (`sid`), and software ID match, including across allowed
origins. The client ID and secret remain the same, while each response contains
tokens with the scope validated for that request. Revoking this client revokes
all tokens issued through it. Different sessions and applications keep separate
clients.

Reuse relies on the existing OIDC session bindings. If a binding is lost or
expires (after 31 days without renewal in Redis), another client can be created.
Existing duplicate clients are not automatically deleted by token exchange.

### POST /auth/session_code

This endpoint can be used by the flagship application in order to create a
session code: this code can be added to the URL of a cozy application (in the
query string, as `session_code`) to create a session. The flagship can create
this code with its access token, and then use it in a webview to avoid the
reauthentication of the user. It can also create the code with the hashed
passphrase (and 2FA if needed) to create a session for the authorize page.

Note that the difference between a `session_code` and a `magic_code` (code in a
magic link sent by email) is the behavior when two-factor authentication is
enabled. The `session_code` will open the session while the `magic_code` will
require the password for that.

#### Request (access token variant)

```http
POST /auth/session_code HTTP/1.1
Host: cozy.example.org
Accept: application/json
Authorization: Bearer eyJpc3Mi...
```

#### Request (passphrase variant)

```http
POST /auth/session_code HTTP/1.1
Host: cozy.example.org
Accept: application/json
Content-Type: application/json
```

```json
{
  "passphrase": "hashed",
  "two_factor_token": "123123123123",
  "two_factor_passcode": "678678"
}
```

#### Response

```http
HTTP/1.1 201 Created
Content-Type: application/json
```

```json
{
  "session_code": "HzEFM3JREpIB6532fQc1FP2t4YJKt3gI"
}
```

The flagship will then be able to open a webview for
`https://cozy-home.example.org/?session_code=HzEFM3JREpIB6532fQc1FP2t4YJKt3gI`.

#### Response (2FA needed)

In case of error where 2FA is needed, the response will be:

```http
HTTP/1.1 403 Forbidden
Content-Type: application/json
```

```json
{
  "error": "two factor needed",
  "two_factor_token": "123123123123"
}
```

### FAQ

> What format is used for tokens?

The access tokens are formatted as [JSON Web Tokens (JWT)](https://jwt.io/),
like this:

| Claim   | Fullname  | What it identifies                                                 |
| ------- | --------- | ------------------------------------------------------------------ |
| `aud`   | Audience  | Identify the recipient where the token can be used (like `access`) |
| `iss`   | Issuer    | Identify the Cozy instance (its domain in fact)                    |
| `iat`   | Issued At | Identify when the token was issued (Unix timestamp)                |
| `sub`   | Subject   | Identify the client that can use the token                         |
| `scope` | Scope     | Identify the scope of actions that the client can accomplish       |

The `scope` is used for [permissions](permissions.md).

Other tokens can be JWT with a similar formalism, or be a simple random value
(when we want to have a clear revocation process).

> What happens when the user has lost her passphrase?

She can reset it from the command-line, like this:

```sh
$ cozy-stack instances reset-passphrase cozy.example.org
ek0Jah1R
```

A new password is generated and print in the console.

> Is two-factor authentication (2FA) possible?

Yes, it's possible. Via the cozy-settings application, the two-factor
authentication can be activated.

Here is how it works in more details:

On each connection, when the 2FA is activated, the user is asked for its
passphrase first. When entering correct passphrase, the user is then asked for:

-   a TOTP (Timebased One-Time password, RFC 6238) derived from a secret
    associated with the instance.
-   a short term timestamped MAC with the same validity time-range and also
    derived from the same secret.

The TOTP is valid for a time range of about 5 minutes. When sending a correct
and still-valid pair `(passcode, token)`, the user is granted with
authentication cookie.

The passcode can be sent to the instance's owner via email — more transport
shall be added later.

### POST /auth/tokens/konnectors/:slug

This endpoint can be used by the flagship application in order to create a
token for the konnector with the given slug. This token can then be used by the
client-side konnector to make requests to cozy-stack.
The flagship app will need to use its own access token to request the konnector 
token.

#### Request

```http
POST /auth/tokens/konnectors/impots HTTP/1.1
Host: cozy.example.org
Accept: application/json
Authorization: Bearer eyJpc3Mi...
```

#### Response

```http
HTTP/1.1 201 Created
Content-Type: application/json
```

```json
"OWY0MjNjMGEtOTNmNi0xMWVjLWIyZGItN2I5YjgwNmRjYzBiCg"
```

### POST /auth/share-by-link/password

This route is used when a share by link is protected by password. The password
can be sent to this route to create a cookie that will allow to use the
sharecode and access the shared page.

#### Request

```http
POST /auth/clients/share-by-link/password HTTP/1.1
Host: cozy.example.org
Content-Type: application/x-www-form-urlencoded

password=HelloWorld!&perm_id=123456789
```

#### Response

```http
HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: pass123...
```

```json
{
  "password": "ok"
}
```

### FAQ

> What format is used for tokens?

The access tokens are formatted as [JSON Web Tokens (JWT)](https://jwt.io/),
like this:

| Claim   | Fullname  | What it identifies                                                      |
| ------- | --------- | ----------------------------------------------------------------------- |
| `aud`   | Audience  | Identify the recipient where the token can be used (i.e. `konn`)        |
| `iss`   | Issuer    | Identify the Cozy instance (its domain in fact)                         |
| `iat`   | Issued At | Identify when the token was issued (Unix timestamp)                     |
| `sub`   | Subject   | Identify the client that can use the token (i.e. the konnector slug)    |
| `scope` | Scope     | Konnector tokens don't have any scope                                   |


## Client-side apps

**Important**: OAuth2 is not used here! The steps looks similar (like obtaining
a token), but when going in the details, it doesn't match.

### How to register the application?

The application is registered at install. See [app management](apps.md) for
details.

### How to get a token?

When a user access an application, she first loads the HTML page. Inside this
page, a token specific to this app is injected (only for private routes), via a
templating method.

We have prefered our custom solution to the implicit grant type of OAuth2 for 2
reasons:

1. It has a better User Experience. The implicit grant type works with 2
   redirections (the application to the stack, and then the stack to the
   application), and the first one needs JS to detect if the token is present or
   not in the fragment hash. It has a strong impact on the time to load the
   application.

2. The implicit grant type of OAuth2 has a severe drawback on security: the
   token appears in the URL and is shown by the browser. It can also be leaked
   with the HTTP `Referer` header.

The token will be given only for the authenticated user. For nested subdomains
(like `calendar.joe.example.net`), the session cookie from the stack is enough
(it is for `.joe.example.net`).

But for flat subdomains (like `joe-calendar.example.net`), it's more
complicated. On the first try of the user, she will be redirected to the stack.
As she is already logged-in, she will be redirected to the app with a session
code (else she can login). This session code can be exchanged to a session
cookie. A redirection will still happen to remove the code from the URL (it
helps to avoid the code being saved in the browser history). For security
reasons, the session code have the following properties:

-   It can only be used once.
-   It is tied to an application (`calendar` in our example).
-   It has a very short time span of validity (1 minute).

### How to use a token?

The token can be sent to the cozy-stack as a `Bearer` token in the
`Authorization` header, like this:

```http
GET /data/io.cozy.events/6494e0ac-dfcb-11e5-88c1-472e84a9cbee HTTP/1.1
Host: cozy.example.org
Authorization: Bearer application-token
```

If the user is authenticated, her cookies will be sent automatically. The
cookies are needed for a token to be valid.

### How to refresh a token?

The token is valid only for 24 hours. If the application is opened for more than
that, it will need to get a new token. But most applications won't be kept open
for so long and it's okay if they don't try to refresh tokens. At worst, the
user just had to reload its page and it will work again.

The app can know it's time to get a new token when the stack starts sending 401
Unauthorized responses. In that case, it can fetches the same html page that it
was loaded initially, parses it and extracts the new token.

## Third-party websites

### How to register the application?

If a third-party websites would like to access a cozy, it had to register first.
For example, a big company can have data about a user and may want to offer her
a way to get her data back in her cozy. When the user is connected on the
website of this company, she can give her cozy address. The website will then
register on this cozy, using the OAuth2 Dynamic Client Registration Protocol, as
explained [above](#post-authregister).

### How to get a token?

To get an access token, it's enough to follow the authorization code flow of
OAuth2:

-   sending the user to the cozy, on the authorize page
-   if the user approves, she is then redirected back to the client
-   the client gets the access code and can exchange it to an access token.

### How to use a token?

The access token can be sent as a bearer token, in the Authorization header of
HTTP:

```http
GET /data/io.cozy.contacts/6494e0ac-dfcb-11e5-88c1-472e84a9cbee HTTP/1.1
Host: cozy.example.org
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
```

### How to refresh a token?

The access token will be valid only for 24 hours. After that, a new access token
must be asked. To do that, just follow the refresh token flow, as explained
[above](#post-authaccess_token).

## Devices and browser extensions

For devices and browser extensions, it is nearly the same than for third-party
websites. The main difficulty is the redirect_uri. In OAuth2, the access code is
given to the client by redirecting the user to an URL controlled by the client.
But devices and browser extensions don't have an obvious URL for that.

The IETF has published an RFC called
[OAuth 2.0 for Native Apps](https://tools.ietf.org/html/draft-ietf-oauth-native-apps-05).

### Native apps on desktop

A desktop native application can start an embedded webserver on localhost. The
redirect_uri will be something like `http://127.0.0.1:19856/callback`.

### Native apps on mobile

On mobile, the native apps can often register a custom URI scheme, like
`com.example.oauthclient:/`. Just be sure that no other app has registered
itself with the same URI.

### Chrome extensions

Chrome extensions can use URL like
`https://<extension-id>.chromiumapp.org/<anything-here>` for their usage. See
https://developer.chrome.com/apps/app_identity#non for more details. It has also
a method to simplify the creation of such an URL:
[`chrome.identity.getRedirectURL`](https://developer.chrome.com/apps/identity#method-getRedirectURL).

### Firefox extensions

It is possible to use an _out of band_ URN: `urn:ietf:wg:oauth:2.0:oob:auto`.
The token is then extracted from the title of the page. See
[this addon for google oauth2](https://github.com/AdrianArroyoCalle/firefox-addons/blob/master/addon-google-oauth2/addon-google-oauth2.js)
as an example.

## Security considerations

The master password, the password known by the user, is derived on the clients
to give two keys. The first key is used to login on the stack, the second key
is used to do client-side encryption. The derivation for the login password is
currently done with the PBKDF2 algorithm (with SHA256), but we have anticipated
the possibility of changing to another algorithm if desirable.

The derived password is stored on the server in a secure fashion, with a
password hashing function. The hashing function and its parameter are stored
with the hash, in order to make it possible to change the algorithm and/or the
parameters later if we had any suspicion that it became too weak. The initial
algorithm is [scrypt](https://pkg.go.dev/golang.org/x/crypto/scrypt).

The access code is valid only once, and will expire after 5 minutes

Dynamically registered applications won't have access to all possible scopes.
For example, an application that has been dynamically registered can't ask the
cozy owner to give it the right to install other applications. This limitation
should improve security, as avoiding too powerful scopes to be used with unknown
applications.

The cozy stack will apply rate limiting to avoid brute-force attacks.

The cozy stack offers
[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)
for most of its services. But it's disabled for `/auth` (it doesn't make sense
here) and for the client-side applications (to avoid leaking their tokens).

The client should really use HTTPS for its `redirect_uri` parameter, but it's
allowed to use HTTP for localhost, as in the native desktop app example.

OAuth2 says that the `state` parameter is optional in the authorization code
flow. But it is mandatory to use it with Cozy.

For more on this subject, here is a list of links:

-   https://www.owasp.org/index.php/Authentication_Cheat_Sheet
-   https://tools.ietf.org/html/rfc6749#page-53
-   https://tools.ietf.org/html/rfc6819
-   https://tools.ietf.org/html/draft-ietf-oauth-closing-redirectors-00
-   http://www.oauthsecurity.com/

## Conclusion

Security is hard. If you want to share some concerns with us, do not hesitate to
send us an email to security AT cozycloud.cc.