Alternative Fortigate Authentication API
Table of Contents
Introduction #
Starting with FortiOS 6.4.2 Fortinet added a new API endpoint for authentication purposes to its firewalls. It can be
used to log in to the system and is an alternative to calling the /logincheck
page as previously seen in other blog
posts such as this one.
The main advantage of this new endpoint is that, just like all other API calls, it uses the JSON format for both the request payload and response body. This makes it easier to parse the answer from the firewall, as it’s no longer necessary to interpret the HTML response. Additionally, this new authentication API endpoint handles pre- and post-login banners gracefully, making ugly workaround unnecessary.
In the next sections I will first give you an overview of the API endpoint before showing some examples.
New authentication API endpoint #
The new authentication API is reachable via the api/v2/authentication
endpoint. It supports different HTTP request
types that are responsible for different use cases:
HTTP request type | Use case |
---|---|
POST | New login request |
PUT | Renewal request to avoid automatic logout |
DELETE | Logout request |
Parameters #
Depending on the chosen type one or more parameters have to be added to the request body so that the use case can
succeed. The following table gives a short overview over the different parameters. Most of them are only important for
the login process via a POST request. Only the session_key
is relevant for PUT and DELETE methods.
Parameter Name | Type | Description |
---|---|---|
username | string | Provides the login username |
secretkey | string | Provides the password for the specified user |
ack_pre_disclaimer | boolean | Used to acknowledge a possible pre-login-banner |
ack_post_disclaimer | boolean | Used to acknowledge a possible post-login-banner |
request_key | boolean | Used to request a session key instead of a cookie, defaults to false |
token_code | string | Used to provide the token code if 2FA is necessary |
new_password1 | string | Used to update the password if a change is needed |
new_password2 | string | Used to update the password if a change is needed |
session_key | string | Provides the session-key if the login process requires it. Also needed for renewal and logout requests |
As indicated by the request_key
option, the API provides two mechanisms to authenticate all future requests once the
login succeeds. If the parameter is set to false, then the endpoint will return a cookie after a successful
authentication. This behavior is similar to the /logincheck
page. If the parameter is set to true, then the endpoint
returns a session key in the answer that has to be included in future requests.
Response Codes #
Once a request is made the API will return a JSON object that consists of the following attribute/value pairs.
JSON attribute | Value type | Value description |
---|---|---|
status_code | integer | Status code |
status_message | string | Message corresponding to the status code |
session_key | string | Session key if it was requested1 |
session_key_timeout | integer | Lifetime of the session key in minutes1 |
pre_login_disclaimer | string | Text of the pre-login banner2 |
post_login_disclaimer | string | Text of the post-login banner2 |
error | string | Error message if 2FA or password change fails3 |
In such a response the following status codes and corresponding messages seem to be possible:
Status Code | Status Message | Description |
---|---|---|
-4 | LOGIN_LOCKED_OUT | Login was unsuccessful because of too many failed previous attempts |
-2 | LOGIN_ACCEPT_PRE_LOGIN_DISCLAIMER | Login failed because the pre-login banner was not accepted |
-1 | LOGIN_FAILED | Login failed because of invalid credentials |
2 | LOGIN_TFA | Login is ongoing. 2FA is required, and a token code must be provided in the next request |
3 | LOGIN_ACCEPT_POST_LOGIN_DISCLAIMER | Login is ongoing, because post login disclaimer was not accepted. The next request must include the ack_post_disclaimer parameter |
4 | LOGIN_CHANGE_PWD_NEEDED | Login is ongoing, because the password needs to be changed. The next request must include the new_password parameters |
5 | LOGIN_SUCCESS | Login was successful |
This concludes the general introduction to this new API endpoint. The next sections show different examples.
Example login with cookies #
In this first example we want to log in as the admin
user to a Fortigate firewall that is reachable under the IP
address 192.168.5.130 and has a post-login banner configured. We also want to use the cookie-based method for
authenticating future requests. Therefore, the API call looks like this:
$ curl -k -X "POST" "https://192.168.5.130/api/v2/authentication" \
-c cookies.txt -H "Content-Type: json" \
-d "{ \"username\": \"admin\", \"secretkey\":\"12345\", \"ack_post_disclaimer\":true}"
{
"status_code":5,
"status_message":"LOGIN_SUCCESS",
"post_login_disclaimer":"POST WARNING: [...]"
}
The status code as well as the corresponding message indicate that the login was successful, and that the cookies have
been saved to the specified cookies.txt
file. Its contents should look something like this and contain a ccsrftoken
and session_key
or apscookie
entry, depending on the FortiOS version used.
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
192.168.5.130 FALSE / FALSE 0 ccsrftoken_443_503fd367 "E35FA3B591A8469EC751BEF3934F4271"
192.168.5.130 FALSE / FALSE 0 session_key_443_503fd367 "801b3cb6f3nx7g6wdNn0zH16hp9wgh"
These cookies can now be used to to authenticate further GET requests. This can be seen in the following excerpt which
retrieves general system information by calling the /api/v2/monitor/system/status
page:
$ curl -k -b cookies.txt \
"https://192.168.5.130/api/v2/monitor/system/status"
{
"http_method":"GET",
"results":{
"model_name":"FortiGate",
"model_number":"VM64",
"model":"FGVM64",
"hostname":"Vladilena",
"log_disk_status":"not_available"
},
"vdom":"root",
"path":"system",
"name":"status",
"action":"",
"status":"success",
"serial":"[...]",
"version":"v7.2.8",
"build":1639
}
Note
When using FortiOS 6.4 the received apscookie
cookie is only valid for a single request and gets invalidated
afterwards. At the same time each request returns a new cookie that can be used to authenticate the next request. It is
therefore necessary to use the -c parameter of curl for each request as well.
Starting with FortiOS 7.0 the behavior seems to have changed, and the cookies are persistent for the duration of the session.
If you want to perform further API calls that use different request methods, such as PUT or POST, one additional step is
necessary. You need to extract the value of the ccsrftoken
cookie from the cookies file and add it as a X-CSRFTOKEN
HTTP header to your request. Otherwise, the firewall will return a 401 Unauthorized
error. The following curl command
showcases this by creating a new address object:
$ curl -k -X "POST" -b cookies.txt \
-H "Content-Type: json" \
-H "X-CSRFTOKEN: E35FA3B591A8469EC751BEF3934F4271" \
"https://192.168.5.130/api/v2/cmdb/firewall/address" \
-d "{ \"name\": \"TestAddress\", \"subnet\": \"1.1.1.1/32\"}"
{
"http_method":"POST",
"revision":"db736bb290ad60ec5c0beff240201d2c",
"revision_changed":true,
"old_revision":"65b48e146cad7b4e4c0346dadde46073",
"mkey":"TestAddress",
"status":"success",
"http_status":200,
"vdom":"root",
"path":"firewall",
"name":"address",
"serial":"[...]",
"version":"v7.2.8",
"build":1639
}
The received cookies have an idle timeout equal to the admintimeout
duration that can be set under config system global
. This means that if no API call is made for the configured duration (defaults to 5 minutes), the user will be
automatically logged out. However, as this is an idle timeout, each successful request that happens before the timer
runs out will reset the duration.
Example login with a session key #
In the second example we want to log in as the admin
user to the same Fortigate firewall that is reachable under the
IP address 192.168.5.130. This time no pre- or post-long banners are configured, and we want to use the session key
mechanism to authenticate future request. Therefore, the API call has the request_key
parameter set to true and looks
like this:
$ curl -k -X "POST" "https://192.168.5.130/api/v2/authentication" \
-H "Content-Type: json" \
-d "{ \"username\": \"admin\", \"secretkey\":\"12345\", \"request_key\":true}"
{
"status_code":5,
"status_message":"LOGIN_SUCCESS",
"session_key":"hsGcgjss4b8073wnk6x07GQ7dxHy71",
"session_key_timeout":"5"
}
The status code and message indicate that the login was successful. Additionally, the Fortigate returns a session key as
well as its timeout value in minutes, that defines how long the key is valid. This session key must now be used to
authenticate further API calls by including it in an Authorization: Bearer
HTTP request header. This can be seen in
the following curl command that uses the session key to retrieve general system information:
$ curl -k -H "Authorization: Bearer hsGcgjss4b8073wnk6x07GQ7dxHy71" \
"https://192.168.5.130/api/v2/monitor/system/status"
{
"http_method":"GET",
"results":{
"model_name":"FortiGate",
"model_number":"VM64",
"model":"FGVM64",
"hostname":"Vladilena",
"log_disk_status":"not_available"
},
"vdom":"root",
"path":"system",
"name":"status",
"action":"",
"status":"success",
"serial":"[...]",
"version":"v7.2.8",
"build":1639
}
The session_key_timeout
value that the firewall returns during the login process is equivalent to the admintimeout
duration that can be set under config system global
. However, in contrast to the cookie-based method the lifetime of a
session key is fixed. This means that it will expire after the given time frame and requests that are made during this
time do not refresh it.
If you want to renew a session you need to execute a PUT request against the api/v2/authentication
endpoint and
include the current session key in the body. If the renewal is successful, the Fortigate will invalidate the current key
and issue a new one that is included in the answer. This can be seen in the following example:
$ curl -k -X "PUT" "https://192.168.5.130/api/v2/authentication" \
-H "Content-Type: json" \
-H "Authorization: Bearer hsGcgjss4b8073wnk6x07GQ7dxHy71"
-d "{ \"session_key\": \"hsGcgjss4b8073wnk6x07GQ7dxHy71\"}"
{
"status_id":"TOKEN_RENEWAL_SUCCESS",
"status":"Token renewal succeeded.",
"session_key":"ybm8kqw608Gt75kNjnQN68mqfy5ymc"
}
Python example #
As seen in the examples above, using curl can be quite tedious for this API depending on the use case. It can therefore be helpful to use a programming language and appropriate libraries to make the job easier. To give you one example I hacked together a bare-bones python script that logs in to a Fortigate firewall, retrieves general system information and creates a new address before logging out. It relies on the requests library.
It can be downloaded here: fgt-api-example.py
Note
Please be aware that the script is just a short example and focuses on showing the API. It’s not intended for production use.
Closing Words #
While this API worked as intended when I wrote this article and performed my tests on FortiOS 6.4. through 7.2 please be aware that it is subject to change with each new FortiOS release.
Thanks for reading and I hope that the provided information in this article was helpful to you. Until the next blog post! 👋