Set Up Authentication Using JSON Web Tokens
You can use a JSON Web Token (JWT) to authenticate with GoodData as an alternative to OpenID Connect. JWTs provide a more modern alternative to simple API access tokens by enabling security features such as expiration time and tamper-proofing.
This article describes how to set up JWT authentication for GoodData and how to use it to secure your API calls:
For an example on how to create JWK and JWT in JavaScript see the Create JWK and JWT in JavaScript article.
Create JWK
A JSON Web Key (JWK) is a key that is needed to verify JSON Web Tokens (JWT). To be able to create and use JWT for authentication you first need to set up a corresponding JWK.
You can store multiple JWKs within a GoodData organization.
Follow these steps:
Generate a Key Pair
We need a key pair (private & public key) to be able to sign JWT and to verify it on the server side. Feel free to follow the instructions below or use your own prefered method.
Steps:
Generate RSA private key:
openssl genrsa -des3 -out private_key.pem 2048
Generate public key:
openssl rsa -in private_key.pem -outform PEM -pubout -out public_key.pem
Generate self-signed certificate:
openssl req -new -x509 -key private_key.pem -subj /CN=client.example.com -days 1000 > certificate.crt
You should now have the following files:
private_key.pem
public_key.pem
- self-signed certificate
certificate.pem
Convert PEM to JWK
Public keys have to be uploaded to GoodData as JWKs, so you have to convert the PEM format to JWK. There are numerous libraries and tools that can be used to help you accomplish this. We will show you how to convert from PEM to JWK using the pem-jwk tool.
Steps:
Install the pem-jwk tool:
npm install -g pem-jwk
Convert PEM to JWK using the following series of commands:
Note that the
x5t
andx5c
fields are optional.kid=$(uuidgen)
x5t=$(openssl x509 -fingerprint -noout -in certificate.crt -sha1 | cut -f2 -d'=' | sed 's/://g' | xxd -r -p | base64 | sed 's/+/-/g; s/\//_/g' | sed 's/=//g')
x5c=$(sed /-/d certificate.crt | tr -d \\n)
pem-jwk public_key.pem | jq --arg kid $kid --arg x5t $x5t --arg x5c $x5c -r '.+{kid: $kid, alg: "RS256", use: "sig", x5t: $x5t, x5c: [$x5c]}'
You should end up with a JWK, similar to this example:
{ "kty": "RSA", "n": "wAwTHQIRVkX4m6lI0ayO1b7FnR4hgH9KFQJPHO7i11zJ6exhs7nzS4WGTlOMzM_j17O3zcBEYfe1P65rhikRhRuYU3cBmqQGxTQEZcTqmOSZxjB7TPukp7R57IvbmYuHFZjxqSQQpazopvCCMHO5OECilT_Md_xuZtdZDehOYNwZM880kN0KKtGFDXDQzC110uk0R_mVatuPY1ZIe0lYnfkokKqfWma849zpcpJE5MiIIxTFsFANsRW3he72EodoDMEhYZnUOQ4dGk_t3OiY-NgtRKtI1vW5T-rsZ0Tl3oRqJmXPeE5TP8bC3n-nm_SJPtDyc2Q-8CO1EITIZR8Ikw", "e": "AQAB", "kid": "67C2BC3D-32E4-4C8C-93EF-9B03F0E65A3F", "alg": "RS256", "use": "sig", "x5t": "oLe3EKODu72OtVftIu8_WGaPWk8", "x5c": [ "MIICtjCCAZ4CCQDH7kDyw9N1hDANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJjbGllbnQuZXhhbXBsZS5jb20wHhcNMjMwODAyMDY1NTMxWhcNMjYwNDI4MDY1NTMxWjAdMRswGQYDVQQDDBJjbGllbnQuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDADBMdAhFWRfibqUjRrI7VvsWdHiGAf0oVAk8c7uLXXMnp7GGzufNLhYZOU4zMz+PXs7fNwERh97U/rmuGKRGFG5hTdwGapAbFNARlxOqY5JnGMHtM+6SntHnsi9uZi4cVmPGpJBClrOim8IIwc7k4QKKVP8x3/G5m11kN6E5g3BkzzzSQ3Qoq0YUNcNDMLXXS6TRH+ZVq249jVkh7SVid+SiQqp9aZrzj3OlykkTkyIgjFMWwUA2xFbeF7vYSh2gMwSFhmdQ5Dh0aT+3c6Jj42C1Eq0jW9blP6uxnROXehGomZc94TlM/xsLef6eb9Ik+0PJzZD7wI7UQhMhlHwiTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACEdhG0PEWR8+cGfs1THIhYdrTyva9Zkb1+T/dqiEpRD3mE3+S0/5pEFcSdgqSwsyZ4CclDlTpjHtMC4ADpqXypB5LAxdyos6IQwDhczB0iDNcgB9FK+xlZjKuHo5Wscmy+B4Mi+7zzZw4t87V8D59ySlXQBbcoi6KSrM9+aY36op2VnA98bSkB4RSi4D7km8PKG1yBiRSGtbL1ihvRu8ZOSftdc3VKqYqgAQVfigKJtIeXgHF0ksN2Hfkwyoskq4nBUUukOKr6pC/OBBA3z74wUQeRUvggyZr0h6OnRHlcYKlt5zdo/JsAk9h23oJvRQ1yRWp7k0XOzF8/kLlwu6Jc=" ] }
The JWK attributes are as follows:
Attributes | Description | Possible Values |
---|---|---|
kty | The family of cryptographic algorithms used with the key. | RSA |
alg | The specific cryptographic algorithm used with the key. | RS256, RS384, RS512 |
use | How the key was meant to be used. | sig - signature |
x5c | (Optional attribute) The x.509 certification chain. The first entry in the array is the certificate to use for token verification; the other certificates can be used to verify this first certificate. | PKIX certificates |
n | The modulus for the RSA public key. | - |
e | The exponent for the RSA public key. | - |
kid | The unique identifier for the key. | string^(?!.)[.A-Za-z0-9_-]{1,255}$ |
x5t | (Optional attribute) The thumbprint of the x.509 cert (SHA-1 thumbprint). | base64url-encoded |
Set New JWK
Once you have a JWK you must upload it to your GoodData server.
In GoodData you manage JWKs using the following API endpoints:
Endpoint | Notes |
---|---|
POST /api/v1/entities/jwks | Set a new JWK |
PUT /api/v1/entities/jwks/<JWK_ID> | Update a JWK with ID |
GET /api/v1/entities/jwks | Get all JWKs |
GET /api/v1/entities/jwks/<JWK_ID> | Get a JWK for given ID |
DELETE /api/v1/entities/jwks/<JWK_ID> | Delete a JWK with ID |
Note that to manage JWKs you need to have the Organization.MANAGE
permission.
Set New JWK for a GoodData Organization
Make the following API call:
curl --request POST \
--header "Authorization: Bearer $API_TOKEN" \
--header 'Content-Type: application/vnd.gooddata.api+json' \
--data '{
"data": {
"id": "jwk-1",
"type": "jwk",
"attributes": {
"content": {
"kty": "RSA",
"n": "wAwTHQIRVkX4m6lI0ayO1b7FnR4hgH9KFQJPHO7i11zJ6exhs7nzS4WGTlOMzM_j17O3zcBEYfe1P65rhikRhRuYU3cBmqQGxTQEZcTqmOSZxjB7TPukp7R57IvbmYuHFZjxqSQQpazopvCCMHO5OECilT_Md_xuZtdZDehOYNwZM880kN0KKtGFDXDQzC110uk0R_mVatuPY1ZIe0lYnfkokKqfWma849zpcpJE5MiIIxTFsFANsRW3he72EodoDMEhYZnUOQ4dGk_t3OiY-NgtRKtI1vW5T-rsZ0Tl3oRqJmXPeE5TP8bC3n-nm_SJPtDyc2Q-8CO1EITIZR8Ikw",
"e": "AQAB",
"kid": "67C2BC3D-32E4-4C8C-93EF-9B03F0E65A3A",
"alg": "RS256",
"use": "sig",
"x5t": "oLe3EKODu72OtVftIu8_WGaPWk8",
"x5c": [
"MIICtjCCAZ4CCQDH7kDyw9N1hDANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJjbGllbnQuZXhhbXBsZS5jb20wHhcNMjMwODAyMDY1NTMxWhcNMjYwNDI4MDY1NTMxWjAdMRswGQYDVQQDDBJjbGllbnQuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDADBMdAhFWRfibqUjRrI7VvsWdHiGAf0oVAk8c7uLXXMnp7GGzufNLhYZOU4zMz+PXs7fNwERh97U/rmuGKRGFG5hTdwGapAbFNARlxOqY5JnGMHtM+6SntHnsi9uZi4cVmPGpJBClrOim8IIwc7k4QKKVP8x3/G5m11kN6E5g3BkzzzSQ3Qoq0YUNcNDMLXXS6TRH+ZVq249jVkh7SVid+SiQqp9aZrzj3OlykkTkyIgjFMWwUA2xFbeF7vYSh2gMwSFhmdQ5Dh0aT+3c6Jj42C1Eq0jW9blP6uxnROXehGomZc94TlM/xsLef6eb9Ik+0PJzZD7wI7UQhMhlHwiTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACEdhG0PEWR8+cGfs1THIhYdrTyva9Zkb1+T/dqiEpRD3mE3+S0/5pEFcSdgqSwsyZ4CclDlTpjHtMC4ADpqXypB5LAxdyos6IQwDhczB0iDNcgB9FK+xlZjKuHo5Wscmy+B4Mi+7zzZw4t87V8D59ySlXQBbcoi6KSrM9+aY36op2VnA98bSkB4RSi4D7km8PKG1yBiRSGtbL1ihvRu8ZOSftdc3VKqYqgAQVfigKJtIeXgHF0ksN2Hfkwyoskq4nBUUukOKr6pC/OBBA3z74wUQeRUvggyZr0h6OnRHlcYKlt5zdo/JsAk9h23oJvRQ1yRWp7k0XOzF8/kLlwu6Jc="
]
}
}
}
}' $HOST_URL/api/v1/entities/jwks
You can confirm the JWK was uploaded correctly by making a GET call to the same API endpoint. Your JWK should be saved as:
{
"data": {
"attributes": {
"content": {
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "67C2BC3D-32E4-4C8C-93EF-9B03F0E65A3A",
"x5t": "oLe3EKODu72OtVftIu8_WGaPWk8",
"x5c": [ "MIICujCCAaKgAwIBAgIECI8fsTANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDExR0ZXN0LWFsZXgucmVhY2g1Lm5ldDAeFw0yMDA3MjkwOTM0MjlaFw0yMjAyMTcxNDIwMzNaMB8xHTAbBgNVBAMTFHRlc3QtYWxleC5yZWFjaDUubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlzRszUeQ4WiSqvmYxMP10ngm8ALIoUwMH7Oa8vrZgD5pqalPjetPAxeVcAv2gTyDlOwtB0fGvlQo6n78pd9pTbgrzUjhmFuYN6OCfT6eN/2wu0LmwryFS2mbh7/1DTiKd2tZaRalskPECXTKkeks85HVqanB0860BYlGvQvfgrvhCWXXFJJeXvNwYNFYdDdrFQhoeOAEvRDKg9DdHZf6XzSR6Qk3w51FKn2b7imen/G52itD/kIen1hqqB2Jwt9SWyX5MSGySY2QwC18F6Dfs8L+t0mwCo6grGW9264Z5vlO0PWssEqGIX/ez6nk1ZdHXhoXwJ0W+6QzeQlUN8jNoQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAETbMWro4HI4ZuqtnjMZrgEOpx6WhAtxpMx5XFPVWbdp/DpPySotoWbbD6qCtYc34E+ec7mH7aHVap+Gl2IyeSHTht4FXfF9q/1Oj/fis/4DDi1iq00rJsU18D71mZ9FGWCWlO1nhW1KSTGbRJ3E0wSrNabcvaXcwEHokR3zm+xfRWjtbrq2hQ19R16xyOLVy4zrF95QxP4UN+Cvm8nmYur6bSqv+gCMvDsl+O/gtRHGgpUukHEJwnee1R3+1aIv+9zOF3HaaUC5neOLBFITGmeXgi8G2IhbG+JoXh/GUkb66TZUlUAM3qXYNL9Nf+2MQ7nAPTXcxlmImFUUrnv0c3"
],
"e": "AQAB",
"n": "wAwTHQIRVkX4m6lI0ayO1b7FnR4hgH9KFQJPHO7i11zJ6exhs7nzS4WGTlOMzM_j17O3zcBEYfe1P65rhikRhRuYU3cBmqQGxTQEZcTqmOSZxjB7TPukp7R57IvbmYuHFZjxqSQQpazopvCCMHO5OECilT_Md_xuZtdZDehOYNwZM880kN0KKtGFDXDQzC110uk0R_mVatuPY1ZIe0lYnfkokKqfWma849zpcpJE5MiIIxTFsFANsRW3he72EodoDMEhYZnUOQ4dGk_t3OiY-NgtRKtI1vW5T-rsZ0Tl3oRqJmXPeE5TP8bC3n-nm_SJPtDyc2Q-8CO1EITIZR8Ikw"
}
},
"id": "jwk-1",
"type": "jwk"
}
}
Create JWT
Once your JWK is created and stored in the GoodData JWKs store, you can generate a JWT for authentication by passing it in the Authorization Bearer
header of your API calls.
Steps:
Prepare a base64 URL encoded header:
$ kid=<jwk_kid> $ jwt_header=$(echo -n "{\"alg\":\"RS256\",\"kid\":\"$kid\"}" | base64 | sed s/\+/-/ | sed -E s/=+$//)
kid
is the JWK unique identifier.
Prepare a base64 URL encoded payload:
$ sub=<userId> $ iat=$(date +%s) $ exp=$(date -v+"1H" +%s) $ jti=$(uuidgen) $ payload=$(echo -n "{\"sub\":\"$sub\",\"name\":\"John Doe\",\"iat\":$iat,\"exp\":$exp,\"jti\":\"$jti\"}" | base64 | sed s/\+/-/ | sed -E s/=+$//)
sub
is the userId of the user in the organization.exp
is the token expiration time.
Create a signature:
$ signature=$(echo -n "$jwt_header.$payload" | openssl dgst -sha256 -binary -sign private_key.pem | openssl enc -base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
Create a signed JWT:
$ signed_jwt="$jwt_header.$payload.$signature"
You can use this signed JWT for authentication.
Make API Calls Using JWT
After uploading your JWK to the GoodData JWKs store, you can make API calls by replacing your API access token with the JWT token. For example:
curl --request GET \
--header "Authorization: Bearer $signed_jwt" \
--header 'Content-Type: application/vnd.gooddata.api+json' \
$HOST_URL/api/v1/profile