Ferrum is a NextGen Auth Server (almost)
Ushakov Michael
Posted on September 29, 2022
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
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:
- Build it with
go build
(optionally re-generate SSL Certificates usinggo generate
); - Edit very simple
config.json
file and specify application endpoint; - Configure you server
realm
,clients
andusers
data (for more details see next paragraph) indata.json
file (in future we'll add store data in NO SQL and user register feature); - 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
orsolution
-
client
= project / application inside realm; -
user
;
Every realm
could contain as many clients
and users
as you wish. Realm structure is:
{
"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:
- For data compatibility with
Keycloak
we must have"info"
struct in every user withpreferred_username
property as a login; - We must have
credentials
struct andpassword
property for a password check, in future we will store only password hashes, and won't store opened passwords.
Advantages:
- 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:
{
"info": {
"phone": "+79996663322",
// other props ...
},
"credentials": {
// other props ...
}
}
- Simple configuration, it enough 1-2 minutes to prepare server data and use;
- Could be running from other application for tests purposes or as part of development / production solution.
- 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:
- For issuing a new token
{baseUrl}/auth/realms/{realm}/protocol/openid-connect/token/
- 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
Minimal code example:
// 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 star
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.
Posted on September 29, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.