# Atlas UM integration example

This repo contains an example on how to use the
[Atlas UM](https://github.com/filtr/atlas-um) user management system to
authenticate and authorize users in Your apps.

The main subject of the interaction with Atlas UM is receiving the access
token for user and later use this token to authorize access to any
protected resources.

The token itself is in JWT format. It can be validated with the public key
and contains all information in a form of claims needed to authorize user
for certain activity in Your apps.

This example demonstrates integration from 2 perspectives: client and
resource server.

## Claims

A claim is an assertion about a user account.
Claims may indicate, for instance, what label a user works
for, what artist a user should see data for, or what role the user
should be granted on a particular resource server, etc.
Claims are discussed the controlling document for JWTs: [RFC
7519](https://tools.ietf.org/html/rfc7519).

When considering how Atlas UM understands claims internally, most
claims can be thought of relationally as the triple:

    ( <account identity>, <claim name>, <claim value> )

The account identity uniquely identifies a user account
within the DnA group, and in many cases uniquely identifies a user of
DnA services. For the purposes of Atlas UM, account identity is
generally equated with the SME-DnA subject assigned to user accounts
by Atlas UM.

The claim name is the term used in Atlas UM to identify a type of
claim that may be made against accounts. For instance, `iat`, `exp`,
and `iss` are claim names in the Atlas UM parlance that are specific
parts of the JWT spec. For claim names not specified by an RFC or
other external document, claim names are uniquely identified by a
URL. For instance, when associating a user account with a label for
use in Decibel services, the claim name may be uniquely identified by
the URL `decibel/label`.

The claim value is the value conveyed by the claim for the given
account identity in the area of the claim name. For instance, the
claim value for an `exp` claim is a Unix epoch timestamp representing
the time at which the token expires, per the JWT spec.

An example of an SME-DnA-specific claim value might be that
within Decibel services some user accounts are assigned a role of User,
in which case the claim name and value might be the following:

```
"decibel/role": ["user"]
```

Or some user account has access to information of some particular labels,
in which case the claim name and value might be the following:

```
"decibel/labels": [
  2,
  3
]

```

Claim values may be any valid JSON value, including a scalar, an object,
or an array.

Putting that all back together, a claim in Atlas UM is composed of: an
account identity, represented by a unique account identifier in the
SME-DnA space, e.g.:

`sme-dna|cf339fa4d5a4b4883597ade0d0fc9be3`

a claim name, represented by a URL, e.g.:

`rti/artists`

and a claim value, represented by JSON value, e.g.:

```
["GRAS_1111111", "GRAS_2222222"]
```

### Token example:

```
{
  "decibel/role": ["user"],
  "decibel/labels": [
    3,
    7
  ],
  "sub": "sme-dna|9a11df36e23da92ddcd0c63af9abf186",
  "email": "test.user@sonymusic.com",
  "name": "Test User",
  "iat": 1628766479,
  "exp": 1628767379,
  "nbf": 1628766479
}
```

## Integration from the client perspective

The primary method of obtaining the access token for client is to get it as a
HTTPOnly cookie.

This process involves several relaying parties:

1. USM - to authenticate user with SME username/password, using 2FA if required
2. Atlas UM - to authorize authenticated user, issuing the access token with
the assigned claims, and refresh token.
3. Atlas auth proxy - to store the cookie for your app domain.

The complete auth flow is the following:

![sequence_diagram.png](sequence_diagram.png)

Let\`s assume that your app is located on the `resource-server.test` and the
Auth proxy is located on the `atlas-auth-proxy` subdomain.

So to start the authentication flow you need to direct the user to
`https://atlas-auth-proxy.resource-server.test/login` url.
This will trigger the process and if the user is successfully authenticated,
the `dna_bearer_token` and `dna_refresh_token` cookie would be assigned for
the `.resource-server.test` domain and the user will be redirected to the
initial referrer url.

It is also possible to pass the `next` param with encoded url where the user
will be redirected at the end of the login flow:

`https://atlas-auth-proxy.resource-server.test/login?next=https%3A%2F%2Ftext.example.com`


If for some reason you need to keep the token somewhere else besides the
cookie, you may use the `/token` endpoint on the auth proxy to get the tokens
in the response body.

So the call to the `https://atlas-auth-proxy.resource-server.test/token` will
give you the response similar to the following:

```
{
  "access_token": "access_token_content.....",
  "refresh_token": "refresh_token_content.....",
  "expires_in": 900,
  "token_type": "Bearer"
}
```

Having the access token, you may call you protected endpoints using it.

Let`s say, you have some SPA app, so there are the following options
how to pass the token to backend:

Using cookie:

```js
fetch(apiUrl, {credentials: "include"})
   .then((response) => {
       if (response.ok) {
           return response.text();
       } else {
           console.log("unauthorized");
       }
   })
   .then((data) => {
       if (data) {
           console.log(data);
       }
   })
```

The same is also true for any web apps with server rendered templates. The
token would be just automatically sent with the request.


Using the `Authorization` header:

```js
fetch(apiUrl, {headers: new Headers({'Authorization': 'Bearer ' + token,})})
   .then((response) => {
       if (response.ok) {
           return response.text();
       } else {
           console.log("unauthorized");
       }
   })
   .then((data) => {
       if (data) {
           console.log(data);
       }
   })
```

The same is also true for any non web application.
You just need to obtain the token as described above, and then pass it with
the request header `Authorization: Bearer <your_access_token>` when calling the
API endpoints.

The access token lifetime is limited to 15 minutes.
But you may yse the refresh token with 30 days lifetime to get the new access
token without calling the full login flow.

So the POST request to the refresh token url
`https://atlas-auth-proxy.resource-server.test/token/refresh`
will give you the response similar to the following:

```
{
  "access_token": "access_token_content.....",
  "refresh_token": "refresh_token_content.....",
  "expires_in": 900,
  "token_type": "Bearer"
}
```

The refresh token may be passed as `refresh_token` param in the request body
or will be passed automatically as `dna_refresh_token` cookie in case of request
from the web browser with credentials.

```js
const formData = new FormData();
formData.append("refresh_token", refreshToken)

fetch(refreshUrl,
 {method: "POST", credentials: "include", data: formData}
)
   .then((response) => {
       if (response.ok) {
         return response.json();
       } else {
           console.log("unauthorized");
       }
   })
   .then((data) => {
       if (data) {
           accessToken = data["access_token"]
           refreshToken = data["refresh_token"]
       }
   })
```

Be sure to also save the new refresh token, as it is rotates on every refresh.


To trigger the logout flow, just point the user to:
`https://atlas-auth-proxy.resource-server.test/logout`

The user will be redirected to the referrer page after the logout by default.
It is possible to pass the `next` or `redirect_uri` param to the logout
endpoint, if you need to point the user to some specific location.

For more realistic  and working example, refer to the JS code in
[templates/spa.html](templates/spa.html)

## Integration from the resource server (backend) perspective

From the backend perspective, there are 2 cases how the authorization could
be implemented.

First one, is when there is no API gateway in front of the app.

There are 2 main points for the app to authorize
the client\`s request is this case:
1. Validate the token with a public key
2. Map the token claims to allowed functionality for specific user

As a brief example, let`s say you have some Flask app. So the authorization
process will look like the following:

```python
from urllib.request import urlopen

from flask import request
import jwt

# get token from either cookies or header

# case with token in cookie
token = request.cookies.get("dna_bearer_token")

# case with token in header, e.g. Authorization: Bearer <token_string>
auth_header = request.headers.get("Authorization", "")
if not token and auth_header.startswith("Bearer "):
    token = auth_header.replace("Bearer ", "")

# get the public key
public_key = urlopen("<shared public key url>")
public_key = public_key.read()
rsa_key = public_key.decode()

# decode the token with validation
payload = jwt.decode(
    token,
    rsa_key,
    algorithms="RS256",
)

# check if the user has access to some protected functionality or data
if payload.get("yourapp/claim") == "some expected value":
    print("Authorized!")
```

Second one, is when the app is located behind the API gateway, that is responsible
for the token validation and decoding.
In this case, your app receives already validated requests with decoded token
payload, located in X-Userinfo HTTP header.

The example:

```python
import json
from jwt.utils import base64url_decode
from flask import request

payload = json.loads(
    base64url_decode(request.headers.get("X-Userinfo"))
)

# check if the user has access to some protected functionality or data
if payload.get("yourapp/claim") == "some expected value":
    print("Authorized!")
```

For more realistic  and working example, refer to the code in
[app.py](app.py)
