Add authentication for data source plugins
Grafana plugins can perform authenticated requests against a third-party API by using the data source proxy or through a custom a backend plugin.
Choose an authentication method
Configure your data source plugin to authenticate against a third-party API in one of either of two ways:
- Use the data source proxy method, or
- Build a backend plugin.
Case | Use |
---|---|
Do you need to authenticate your plugin using Basic Auth or API keys? | Use the data source proxy. |
Does your API support OAuth 2.0 using client credentials? | Use the data source proxy. |
Does your API use a custom authentication method that isn't supported by the data source proxy? | Use a backend plugin. |
Does your API communicate over a protocol other than HTTP? | Use a backend plugin. |
Does your plugin require alerting support? | Use a backend plugin. |
Encrypt data source configuration
Data source plugins have two ways of storing custom configuration: jsonData
and secureJsonData
.
Users with the Viewer role can access data source configuration such as the contents of jsonData
in cleartext. If you've enabled anonymous access, anyone who can access Grafana in their browser can see the contents of jsonData
.
Users of Grafana Enterprise can restrict access to data sources to specific users and teams. For more information, refer to Data source permissions.
You can see the settings that the current user has access to by entering window.grafanaBootData
in the developer console of your browser.
Do not use jsonData
with sensitive data such as passwords, tokens, and API keys. If you need to store sensitive information, use secureJsonData
instead.
Store configuration in secureJsonData
If you need to store sensitive information, use secureJsonData
instead of jsonData
. Whenever the user saves the data source configuration, the secrets in secureJsonData
are sent to the Grafana server and they're encrypted before they're stored.
Once you have encrypted the secure configuration, it can no longer be accessed from the browser. The only way to access secrets after they've been saved is by using the data source proxy.
Add secrets configuration to your data source plugin
To add secrets to a data source plugin, you can add support for configuring an API key.
-
Create a new interface in
types.ts
to hold the API key:export interface MySecureJsonData {
apiKey?: string;
} -
Add type information to your
secureJsonData
object by updating the props for yourConfigEditor
to accept the interface as a second type parameter. Access the value of the secret from theoptions
prop inside yourConfigEditor
:interface Props extends DataSourcePluginOptionsEditorProps<MyDataSourceOptions, MySecureJsonData> {}
const { secureJsonData, secureJsonFields } = options;
const { apiKey } = secureJsonData;noteYou can do this until the user saves the configuration; when the user saves the configuration, Grafana clears the value. After that, you can use
secureJsonFields
to determine whether the property has been configured. -
To securely update the secret in your plugin's configuration editor, update the
secureJsonData
object using theonOptionsChange
prop:const onAPIKeyChange = (event: ChangeEvent<HTMLInputElement>) => {
onOptionsChange({
...options,
secureJsonData: {
apiKey: event.target.value,
},
});
}; -
Define a component that can accept user input:
<Input
type="password"
placeholder={secureJsonFields?.apiKey ? 'configured' : ''}
value={secureJsonData.apiKey ?? ''}
onChange={onAPIKeyChange}
/> -
Optional: If you want the user to be able to reset the API key, then you need to set the property to
false
in thesecureJsonFields
object:const onResetAPIKey = () => {
onOptionsChange({
...options,
secureJsonFields: {
...options.secureJsonFields,
apiKey: false,
},
secureJsonData: {
...options.secureJsonData,
apiKey: '',
},
});
};
Once users can configure secrets, the next step is to see how we can add them to our requests.
Authenticate using the data source proxy
Once the user has saved the configuration for a data source, the secret data source configuration will no longer be available in the browser. Encrypted secrets can only be accessed on the server. So how do you add them to your request?
The Grafana server comes with a proxy that lets you define templates for your requests: proxy routes. Grafana sends the proxy route to the server, decrypts the secrets along with other configuration, and adds them to the request before sending it.
Be sure not to confuse the data source proxy with the auth proxy. The data source proxy is used to authenticate a data source, while the auth proxy is used to log into Grafana itself.
Add a proxy route to your plugin
To forward requests through the Grafana proxy, you need to configure one or more proxy routes. A proxy route is a template for any outgoing request that is handled by the proxy. You can configure proxy routes in the plugin.json file.
-
Add the route to
plugin.json
:src/plugin.json"routes": [
{
"path": "example",
"url": "https://api.example.com"
}
]noteYou need to build your plugin and restart the Grafana server every time you make a change to your
plugin.json
file. -
In the
DataSource
, extract the proxy URL frominstanceSettings
to a class property calledurl
:export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
url?: string;
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
super(instanceSettings);
this.url = instanceSettings.url;
}
// ...
} -
In the
query
method, make a request usingBackendSrv
. The first section of the URL path needs to match thepath
of your proxy route. The data source proxy replacesthis.url + routePath
with theurl
of the route. Based on our example, the URL for the request would behttps://api.example.com/v1/users
:import { getBackendSrv } from '@grafana/runtime';
const routePath = '/example';
getBackendSrv().datasourceRequest({
url: this.url + routePath + '/v1/users',
method: 'GET',
});
Add a dynamic proxy route to your plugin
Grafana sends the proxy route to the server, where the data source proxy decrypts any sensitive data and interpolates the template variables with the decrypted data before making the request.
To add user-defined configuration to your routes:
-
Use
.JsonData
for configuration stored injsonData
. For example, whereprojectId
is the name of a property in thejsonData
object:src/plugin.json"routes": [
{
"path": "example",
"url": "https://api.example.com/projects/{{ .JsonData.projectId }}"
}
] -
Use
.SecureJsonData
for sensitive data stored insecureJsonData
. For example, wherepassword
is the name of a property in thesecureJsonData
object:src/plugin.json"routes": [
{
"path": "example",
"url": "https://{{ .JsonData.username }}:{{ .SecureJsonData.password }}@api.example.com"
}
]
In addition to adding the URL to the proxy route, you can also add headers, URL parameters, and a request body.
Add HTTP headers to a proxy route
Here's an example of adding name
and content
as HTTP headers:
"routes": [
{
"path": "example",
"url": "https://api.example.com",
"headers": [
{
"name": "Authorization",
"content": "Bearer {{ .SecureJsonData.apiToken }}"
}
]
}
]
Add URL parameters to a proxy route
Here's an example of adding name
and content
as URL parameters:
"routes": [
{
"path": "example",
"url": "http://api.example.com",
"urlParams": [
{
"name": "apiKey",
"content": "{{ .SecureJsonData.apiKey }}"
}
]
}
]
Be aware that urlParams
configuration is only supported in data source plugins. It is not supported in app plugins.
Add a request body to a proxy route
Here's an example of adding username
and password
to the request body:
"routes": [
{
"path": "example",
"url": "http://api.example.com",
"body": {
"username": "{{ .JsonData.username }}",
"password": "{{ .SecureJsonData.password }}"
}
}
]
Add an OAuth 2.0 proxy route to your plugin
Since your request to each route is made server-side with OAuth 2.0 authentication, only machine-to-machine requests are supported. In order words, if you need to use a different grant than client credentials, you need to implement it yourself.
To authenticate using OAuth 2.0, add a tokenAuth
object to the proxy route definition. If necessary, Grafana performs a request to the URL defined in tokenAuth
to retrieve a token before making the request to the URL in your proxy route. Grafana automatically renews the token when it expires.
Any parameters defined in tokenAuth.params
are encoded as application/x-www-form-urlencoded
and sent to the token URL.
{
"routes": [
{
"path": "api",
"url": "https://api.example.com/v1",
"tokenAuth": {
"url": "https://api.example.com/v1/oauth/token",
"params": {
"grant_type": "client_credentials",
"client_id": "{{ .SecureJsonData.clientId }}",
"client_secret": "{{ .SecureJsonData.clientSecret }}"
}
}
}
]
}
Be aware that tokenAuth
configuration is only supported in data source plugins. It is not supported in app plugins.
Authenticate using a backend plugin
While the data source proxy supports the most common authentication methods for HTTP APIs, using proxy routes has a few limitations:
- Proxy routes only support HTTP or HTTPS.
- Proxy routes don't support custom token authentication.
If any of these limitations apply to your plugin, you need to add a backend plugin. Because backend plugins run on the server, they can access decrypted secrets, which makes it easier to implement custom authentication methods.
The decrypted secrets are available from the DecryptedSecureJSONData
field in the instance settings.
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
instanceSettings := req.PluginContext.DataSourceInstanceSettings
if apiKey, exists := settings.DecryptedSecureJSONData["apiKey"]; exists {
// Use the decrypted API key.
}
// ...
}