Secure an api with api keys
Before you start
If you already have an up and running otoroshi instance, you can skip the following instructions
Let’s start by downloading the latest Otoroshi.
curl -L -o otoroshi.jar 'https://github.com/MAIF/otoroshi/releases/download/v16.20.0-dev/otoroshi.jar'
then you can run start Otoroshi :
java -Dotoroshi.adminPassword=password -jar otoroshi.jar
Now you can log into Otoroshi at http://otoroshi.oto.tools:8080 with admin@otoroshi.io/password
Create a simple route
From UI
- Navigate to http://otoroshi.oto.tools:8080/bo/dashboard/routes and click on the
create new route
button - Give a name to your route
- Save your route
- Set
myservice.oto.tools
as frontend domains - Set
https://request.otoroshi.io
as backend target (hostname:request.otoroshi.io
, port:443
, Tls:Enabled
)
From Admin API
curl -X POST http://otoroshi-api.oto.tools:8080/api/routes \
-H "Content-type: application/json" \
-u admin-api-apikey-id:admin-api-apikey-secret \
-d @- <<'EOF'
{
"id": "myservice",
"name": "myapi",
"frontend": {
"domains": ["myservice.oto.tools"]
},
"backend": {
"targets": [
{
"hostname": "request.otoroshi.io",
"port": 443,
"tls": true
}
]
}
}
EOF
Secure routes with api key
By default, a route is public. In our case, we want to secure all paths starting with /api
and leave all others unauthenticated.
Let’s add a new plugin, called Apikeys
, to our route. Search in the list of plugins, then add it to the flow. Once done, restrict its range by setting up /api
in the Informations>include
section.
From Admin API
curl -X PUT http://otoroshi-api.oto.tools:8080/api/routes/myservice \
-H "Content-type: application/json" \
-u admin-api-apikey-id:admin-api-apikey-secret \
-d @- <<'EOF'
{
"id": "myservice",
"name": "myapi",
"frontend": {
"domains": ["myservice.oto.tools"]
},
"backend": {
"targets": [
{
"hostname": "request.otoroshi.io",
"port": 443,
"tls": true
}
]
},
"plugins": [
{
"enabled": true,
"plugin": "cp:otoroshi.next.plugins.ApikeyCalls",
"include": [
"/api"
],
"config": {
"validate": true,
"mandatory": true,
"wipe_backend_request": true,
"update_quotas": true
}
}
]
}
EOF
Navigate to http://myservice.oto.tools:8080/api/test again. If the service is configured, you should have a Service Not found error
.
The expected error on the /api/test
, indicate that an api key is required to access to this part of the backend service.
Navigate to any other routes which are not starting by /api/*
like http://myservice.oto.tools:8080/test/bar
Generate an api key to request secure services
Navigate to http://otoroshi.oto.tools:8080/bo/dashboard/apikeys/add or when clicking on the Add apikey button on the sidebar.
The only required fields of an Otoroshi api key are :
ApiKey id
ApiKey Secret
ApiKey Name
These fields are automatically generated by Otoroshi. However, you can override these values and indicate an additional description.
To simplify the rest of the tutorial, set the values:
my-first-api-key-id
asApiKey Id
my-first-api-key-secret
asApiKey Secret
Click on Create and stay on this ApiKey button at the bottom of the page.
Now you created the key, it’s time to call our previous generated service with it.
Otoroshi supports 4 methods to achieve that:
First one by passing Otoroshi api key in two headers : Otoroshi-Client-Id
and Otoroshi-Client-Secret
(these headers names can be override on each service). The second by passing Otoroshi api key in the authentication Header (basically the Authorization
header) as a basic encoded value. The third option is to use the bearer generated for your apikey (you can get it by calling curl http://otoroshi-api.oto.tools:8080/api/apikeys/my-first-api-key-id/bearer
). A fourth option is to use jwt token but we will not review it here but you can find a tutorial here.
Let’s go ahead and call our service with the first method :
curl -X GET \
-H 'Otoroshi-Client-Id: my-first-api-key-id' \
-H 'Otoroshi-Client-Secret: my-first-api-key-secret' \
'http://myservice.oto.tools:8080/api/test' --include
then with the second method using basic authentication:
curl -X GET \
-H 'Authorization: Basic bXktZmlyc3QtYXBpLWtleS1pZDpteS1maXJzdC1hcGkta2V5LXNlY3JldA==' \
'http://myservice.oto.tools:8080/api/test' --include
or
curl -X GET \
-u my-first-api-key-id:my-first-api-key-secret \
'http://myservice.oto.tools:8080/api/test' --include
then with the third method using otoroshi bearer:
curl -X GET \
-H 'Authorization: Bearer otoapk_my-first-api-key-id_99cb8e081d692044593ad0e658a67a5114f7afbdcbeb26f8087cce0df3b610b2' \
'http://myservice.oto.tools:8080/api/test' --include
Tips : To easily fill your headers, you can jump to the
Call examples
section in each api key view. In this section the header names are the default values and the service url is not set. You have to adapt these lines to your case.
Override defaults headers names for a route
In some case, we want to change the defaults headers names (and it’s a quite good idea).
Let’s start by navigating to the Apikeys
plugin in the Designer of our route.
The first values to change are the headers names used to read the api key from client. Start by clicking on extractors > CustomHeaders
and set the following values :
api-key-header-id
asCustom client id header name
api-key-header-secret
asCustom client secret header name
Save the route, and call the service again.
curl -X GET \
-H 'Otoroshi-Client-Id: my-first-api-key-id' \
-H 'Otoroshi-Client-Secret: my-first-api-key-secret' \
'http://myservice.oto.tools:8080/api/test' --include
This should output an error because Otoroshi are expecting the api keys in other headers.
{
"Otoroshi-Error": "No ApiKey provided"
}
Call one again the service but with the changed headers names.
curl -X GET \
-H 'api-key-header-id: my-first-api-key-id' \
-H 'api-key-header-secret: my-first-api-key-secret' \
'http://myservice.oto.tools:8080/api/test' --include
All others default services will continue to accept the api keys with the Otoroshi-Client-Id
and Otoroshi-Client-Secret
headers, whereas our service, will accept the api-key-header-id
and api-key-header-secret
headers.
Accept only api keys with expected values
By default, a secure service only accepts requests with api key. But all generated api keys are eligible to call our service and in some case, we want authorize only a couple of api keys.
You can restrict the list of accepted api keys by giving a list of metadata
or/and tags
. Each api key has a list of tags
and metadata
, which can be used by Otoroshi to validate a request with an api key. All api key metadata/tags can be forward to your service (see Otoroshi Challenge
section of a service to get more information about Otoroshi info. token
).
Let’s starting by only accepting api keys with the otoroshi
tag.
Click on the ApiKeys
plugin, and enabled the Routing
section. These constraints guarantee that a request will only be transmitted if all the constraints are validated.
In our first case, set otoroshi
in One Tag in
array and save the service. Then call our service with :
curl -X GET \
-H 'Otoroshi-Client-Id: my-first-api-key-id' \
-H 'Otoroshi-Client-Secret: my-first-api-key-secret' \
'http://myservice.oto.tools:8080/api/test' --include
This should output :
// Error reason : Our api key doesn't contains the expected tag.
{
"Otoroshi-Error": "Bad API key"
}
Navigate to the edit page of our api key, and jump to the Metadata and tags
section. In this section, add otoroshi
in Tags
array, then save the api key. Call once again your call and you will normally get a successful response of our backend service.
In this example, we have limited our service to API keys that have otoroshi
as a tag.
Otoroshi provides a few others behaviours. For each behaviour, Api key used should:
All Tags in
: have all of the following tagsNo Tags in
: not have one of the following tagsOne Tag in
: have at least one of the following tags
All Meta. in
: have all of the following metadata entriesNo Meta. in
: not have one of the following metadata entriesOne Meta. in
: have at least one of the following metadata entries
One Meta key in
: have at least one of the following key in metadataAll Meta key in
: have all of the following keys in metadataNo Meta key in
: not have one of the following keys in metadata