Ferrum is a NextGen Auth Server (almost)
Tags: go, opensource, tutorial, programming
Published: 2022-09-29 12:09:49

## Authorization and Why we created `Ferrum` project

Nowadays, almost all distributed, web or client-server systems have authorization to restrict access only to those users who have rights,**`OAuth2.0`** and **`OpenId-Connect`** are **_most popular and convenient technologies for users authorization_**. There are some well-known authorization servers, i.e. `KeyCloak`. 

Personally, I like `KeyCloak` and **_use it where it is possible_**. About 2 years ago I had to expand my technology stack and I started to write apps using `Golang`. These apps are `WEB API` that use `Authorization` on `KeyCloak` server. When `API` grows and **_become more complicated, it is very important to make automated integration tests_**. For authorization we using separate from **_demo/production_** server, and it is important to **_make code portable_** to any machine, therefore the most convenient way is **_to start all infrastructure before any test and stop when test ends with any persistent data cleanup at the end_**. Such approach **_can't be easily and fast implemented using `Keycloak`_**. Therefore, we created [`Ferrum`](https://github.com/Wissance/Ferrum) authorization server that could be run and stopped from code and with API compatible with  `KeyCloak` (with similar behavior and endpoints). Ferrum could be used **_not only for tests purposes but also like a full-fledged authorization server_**. 

This is **_a tutorial how easily_** (in 1-2 minutes) **_add authorization_** to your solutions or how **_to write integrational tests_** on `golang` web api also in **1-2 min**.

## Build, configure, and run authorization server

To use `Ferrum` as a standalone authorization server, you need:
1. Build it with `go build` (optionally re-generate SSL 
   Certificates using `go generate`);
2. Edit very simple `config.json` file and specify application 
   endpoint;
3. Configure you server `realm`, `clients` and `users` data (for 
   more details see next paragraph) in `data.json` file (in future 
   we'll add store data in NO SQL and user register feature);
4. Run executable `./Ferrum`, you authorization server is up. 
   That's all.

## Configure server data

Let's consider data configuration more detailed. Data is similar to what we are having on `Keycloak`. We have 3 types of objects for version `0.1.0`:

* `realm` = `organization` or `solution` 
* `client` = project / application inside realm;
* `user`;

Every `realm` could contain as many `clients` and `users` as you wish. Realm structure is:
```json
{
    "name": "myapp",
    "token_expiration": 330,
    "refresh_expiration": 200,
    "clients": [
     {
         "id": "d4dc483d-7d0d-4d2e-a0a0-2d34b55e5a14",
         "name": "test-service-app-client",
         "type": "confidential",
         "auth": {
                     "type": 1,
                     "value": "fb6Z4RsOadVycQoeQiN57xpu8w8wplYz"
         }
     } ],
     "users": [
      {
          "info": {
              "sub": "667ff6a7-3f6b-449b-a217-6fc5d9ac0723",
              "email_verified": false,
              "roles": [
                  "admin"
               ],
               "name": "admin sys",
               "preferred_username": "admin",
               "given_name": "admin",
               "family_name": "sys"
          },
          "credentials": {
               "password": "1s2d3f4g90xs"
          }
     }
     ]
```

**Restrictions:**
1. For data compatibility with `Keycloak` we must have `"info"` 
   struct in every user with `preferred_username` property as a 
   login;
2. We must have `credentials` struct and `password` property for
   a password check, in future we will store only password 
   hashes, and won't store opened passwords.

**Advantages:**
1. We could have userinfo of **_any complexity_**  (info structure 
   in every user, see upper json) and we should not make some 
   changes in the code. I.e. we like to add phone number, easy:
   ```json
   {
       "info": {
           "phone": "+79996663322",
           // other props ...
       },
       "credentials": {
            // other props ...
       }
   }
   ```
2. Simple configuration, it enough 1-2 minutes to prepare server 
   data and use;
3. Could be running from other application for tests purposes or 
   as part of development / production solution.
4. API compatible with KeyCloak, you could easily switch to one 
   technological stack in future when Ferrum will have all 
   necessary features.

## Ferrum REST API
 For version `v0.1.0` we have only 2 endpoints:
 1. For issuing a new token 
    `{baseUrl}/auth/realms/{realm}/protocol/openid-connect/token/`
 2. For getting user info that issued this token 
    `{baseUrl}/auth/realms/{realm}/protocol/openid-connect/userinfo/`

All these REST API  

## Write integrational tests with go
A full working example could be found in our integation test [here](https://github.com/Wissance/Ferrum/blob/master/application_test.go)
Minimal code example:
```go
// 1. init app, appConfig, testServerData and testKey are variables
app := CreateAppWithData(appConfig, &testServerData, testKey)
// 2. start the app
res, err = app.Start()
// 3. issue new token (taken from func)
// baseUrl, realm and other token issue params are variables
tokenUrlTemplate := "{0}/auth/realms/{1}/protocol/openid-connect/token/"
tokenUrl := stringFormatter.Format(tokenUrlTemplate, baseUrl, realm)
getTokenData := url.Values{}
getTokenData.Set("client_id", clientId)
getTokenData.Set("client_secret", clientSecret)
getTokenData.Set("scope", "profile")
getTokenData.Set("grant_type", "password")
getTokenData.Set("username", userName)
getTokenData.Set("password", password)
response, err := http.PostForm(tokenUrl, getTokenData)
// 4. get a token and use
responseBody, err := io.ReadAll(response.Body)
var result dto.Token
err = json.Unmarshal(responseBody, &result)
stationEndpointTemplate := "{0}/api/station/"
getStationsUrl := stringFormatter.Format(stationEndpointTemplate, baseServiceUrl)
client := http.Client{}
request, err := http.NewRequest("GET",getStationsUrl, nil)
	request.Header.Set("Authorization", "Bearer "+ result.AccessToken)
response, err := client.Do(request)
// todo(UMV): parse response
// 5. stop app
app.Stop()
```

## Conclusion
Thanks for reading, please help our project grow, please add us a github <a class="github-button" href="https://github.com/wissance/Ferrum" data-icon="octicon-star" aria-label="Star wissance/Ferrum on GitHub">star</a>
Our project will grow anyway, we are planning to add:
* data store in one of NoSQL databases;
* secure credential store;
* integrations with external users provider like `LDAP`;
* integrations with social providers like google;
* possibility to authorize using biometrical data.