Consume external API using Maskinporten¶
This how-to guides you through the steps required to consume an API secured with Maskinporten:
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:
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 must have the Content-Type
header 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.
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 a new token, you can finally consume the external API by using the token as a Bearer token: