Skip to content

Consume external API using Maskinporten

This how-to guides you through the steps required to consume an API secured with Maskinporten:

  1. Configure your application
  2. Acquire tokens from Maskinporten
  3. Consume the API using the token

Configure your application

Declare all the scopes that you want to consume in your application's NAIS manifest so that your application is granted access to them:

nais.yaml
spec:
  maskinporten:
    enabled: true
    scopes:
      consumes:
        - name: "skatt:some.scope"
        - name: "nav:some/other/scope"

The scopes themselves are defined and owned by the external API provider. The exact scope values must be exchanged out-of-band.

Ensure that organization has access to scopes

Make sure that the provider has granted NAV (organization number 889640782) access to any scopes that you wish to consume.

Provisioning of client will fail otherwise.

Use webproxy for outbound network connectivity from on-premises environments

If you're on-premises, you must enable and use webproxy to access Maskinporten.

Configure appropriate outbound access policies to access the external API endpoints.

Acquire token

To acquire a token from Maskinporten, you will need to create a client assertion.

Create client assertion

The client assertion is a JWT that consists of a header, a payload and a signature.

The header should consist of the following parameters:

Parameter Value Description
kid <kid-from-JWK> The key identifier of the private JWK used to sign the assertion. The private key is found in the MASKINPORTEN_CLIENT_JWK environment variable.
typ JWT Represents the type of this JWT. Set this to JWT.
alg RS256 Represents the cryptographic algorithm used to secure the JWT. Set this to RS256.

The payload should have the following claims:

Claim Example Value Description
aud https://test.maskinporten.no/ The audience of the token. Set to the MASKINPORTEN_ISSUER environment variable.
iss 60dea49a-255b-48b5-b0c0-0974ac1c0b53 The issuer of the token. Set to the MASKINPORTEN_CLIENT_ID environment variable.
scope nav:test/api scope is a whitespace-separated list of scopes that you want in the issued token from Maskinporten.
iat 1698435010 iat stands for issued at. Set to now.
exp 1698435070 exp is the expiration time. Between 1 and 120 seconds after now. Typically 30 seconds is fine
jti 2d1a343c-6e7d-4ace-ae47-4e77bcb52db9 The JWT ID of the token. Used to uniquely identify a token. Set this to a unique value such as an UUID.

If the API provider requires the use of an audience-restricted token, you must also include the following claim:

Claim Example Value Description
resource https://api.some-provider.no/ Target audience for the token returned by Maskinporten. The exact value is defined by the API provider and exchanged out-of-band.

Finally, create a signature for the client assertion.

Example Code for Creating a Client Assertion

The sample code below shows how to create and sign a client assertion in a few different languages:

Minimal example code for creating a client assertion in Kotlin, using Nimbus JOSE + JWT.

import com.nimbusds.jose.*
import com.nimbusds.jose.crypto.*
import com.nimbusds.jose.jwk.*
import com.nimbusds.jwt.*
import java.time.Instant
import java.util.Date
import java.util.UUID

val clientId: String = System.getenv("MASKINPORTEN_CLIENT_ID")
val clientJwk: String = System.getenv("MASKINPORTEN_CLIENT_JWK")
val issuer: String = System.getenv("MASKINPORTEN_ISSUER")
val scope: String = "nav:test/api"
val rsaKey: RSAKey = RSAKey.parse(clientJwk)
val signer: RSASSASigner = RSASSASigner(rsaKey.toPrivateKey())

val header: JWSHeader = JWSHeader.Builder(JWSAlgorithm.RS256)
    .keyID(rsaKey.keyID)
    .type(JOSEObjectType.JWT)
    .build()

val now: Date = Date.from(Instant.now())
val expiration: Date = Date.from(Instant.now().plusSeconds(60))
val claims: JWTClaimsSet = JWTClaimsSet.Builder()
    .issuer(clientId)
    .audience(issuer)
    .issueTime(now)
    .claim("scope", scope)
    .expirationTime(expiration)
    .jwtID(UUID.randomUUID().toString())
    .build()

val jwtAssertion: String = SignedJWT(header, claims)
    .apply { sign(signer) }
    .serialize()

Minimal example code for creating a client assertion in Python, using PyJWT.

import json, jwt, os, uuid
from datetime import datetime, timezone, timedelta
from jwt.algorithms import RSAAlgorithm

issuer = os.getenv('MASKINPORTEN_ISSUER')
jwk = os.getenv('MASKINPORTEN_CLIENT_JWK')
client_id = os.getenv('MASKINPORTEN_CLIENT_ID')

header = {
    "kid": json.loads(jwk)['kid']
}

payload = {
    "aud": issuer,
    "iss": client_id,
    "scope": "nav:test/api",
    "iat": datetime.now(tz=timezone.utc),
    "exp": datetime.now(tz=timezone.utc)+timedelta(minutes=1),
    "jti": str(uuid.uuid4())
}

private_key = RSAAlgorithm.from_jwk(jwk)
jwtAssertion = jwt.encode(payload, private_key, "RS256", header)

Request token from Maskinporten

Request

The token request is an HTTP POST request. It should have the Content-Type set to application/x-www-form-urlencoded

The body of the request should contain the following parameters:

Parameter Value Description
grant_type urn:ietf:params:oauth:grant-type:jwt-bearer Type of grant the client is sending. Always urn:ietf:params:oauth:grant-type:jwt-bearer.
assertion eyJraWQ... The client assertion itself. It should be unique and only used once.

Send the request to the token_endpoint, i.e. the URL found in the MASKINPORTEN_TOKEN_ENDPOINT environment variable:

POST ${MASKINPORTEN_TOKEN_ENDPOINT} HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&
assertion=eY...

Response

Maskinporten will respond with a JSON object that contains the access token. Your application does not need to validate this token.

{
    "access_token": "eyJraWQ...",
    "expires_in": 3599,
    ...
}
Cache your tokens

The expires_in field in the response indicates the lifetime of the token in seconds.

Use this field to cache and reuse the token to minimize network latency impact.

See the Maskinporten token documentation for more details.

Consume API

Once you have acquired the token, you can finally consume the external API.

Use the token in the Authorization header as a Bearer token:

GET /resource HTTP/1.1

Host: api.example.com
Authorization: Bearer eyJraWQ...