Skip to main content
  1. Posts/

Alternative Fortigate Authentication API

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! 👋


  1. Value will be missing if request_key was set to false in the request ↩︎ ↩︎

  2. Value will be missing if no banner is configured on the firewall ↩︎ ↩︎

  3. Value will be missing if no 2FA or password change related error occurs ↩︎