Skip to main content

Client Credentials plugin

Client Credentials plugin

this plugin can be used implement the OAuth2 client_credentials flow with the access_token being a biscuit. this plugin is of kind Backend. It expects a POST with a typical OAuth2 client_credentials flow payload.

{
"grant_type": "client_credentials",
"client_id": "apikey_client_id",
"client_secret": "apikey_client_secret",
"bearer_kind": "biscuit",
"aud": "https://api.foo.bar" // optional
}

Demo

let's try to implement a route protected by a biscuit tokens verifier where the token is issued by the client_credentials plugin

Setup the client_credentials plugin

first let's create an apikey

curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic xxxx' \
"http://otoroshi-api.oto.tools:8080/apis/apim.otoroshi.io/v1/apikeys" \
-d '{
"_loc" : {
"tenant" : "default",
"teams" : [ "default" ]
},
"clientId" : "N5COev7kEpPBrbVg",
"clientSecret" : "iDba0Ahz2AMYU7Ao",
"clientName" : "test",
"description" : "",
"authorizedGroup" : null,
"authorizedEntities" : [ "route_bfda6474-0f81-4880-966e-8dae5c2683de", "route_4874704c-56a2-4460-9a21-ff8055a19c75" ],
"authorizations" : [ {
"kind" : "route",
"id" : "bfda6474-0f81-4880-966e-8dae5c2683de"
}, {
"kind" : "route",
"id" : "4874704c-56a2-4460-9a21-ff8055a19c75"
} ],
"enabled" : true,
"readOnly" : false,
"allowClientIdOnly" : false,
"throttlingQuota" : 10000000,
"dailyQuota" : 10000000,
"monthlyQuota" : 10000000,
"constrainedServicesOnly" : false,
"restrictions" : {
"enabled" : false,
"allowLast" : true,
"allowed" : [ ],
"forbidden" : [ ],
"notFound" : [ ]
},
"rotation" : {
"enabled" : false,
"rotationEvery" : 744,
"gracePeriod" : 168,
"nextSecret" : null
},
"validUntil" : null,
"tags" : [ ],
"metadata" : { }
}'

then let's create a keypair

curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic xxxx' \
"http://otoroshi-api.oto.tools:8080/apis/biscuit.extensions.cloud-apim.com/v1/biscuit-keypairs" \
-d '{
"id" : "biscuit-keypair_dev_d25612c6-b4d0-43ed-a711-16b6c09a5155",
"name" : "New Biscuit Key Pair",
"description" : "New biscuit KeyPair",
"metadata" : { },
"pubKey" : "771F9E7FE62784502FE34CE862220586D3DB637D6A5ABAD254F7330369D3B357",
"privKey" : "4379BE5B9AFA1A84F59D2417C20020EF1E47E0805945535B45616209D8867E50",
"tags" : [ ]
}'

then let's create a forge

curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic xxxx' \
"http://otoroshi-api.oto.tools:8080/apis/biscuit.extensions.cloud-apim.com/v1/biscuit-forges" \
-d '{
"id" : "biscuit-forge_dev_7580094c-47e0-495e-80fc-b9c9e8fb8129",
"name" : "New biscuit token",
"description" : "New biscuit token",
"metadata" : { },
"keypair_ref" : "biscuit-keypair_dev_d25612c6-b4d0-43ed-a711-16b6c09a5155",
"config" : {
"checks" : [ ],
"facts" : [ ],
"resources" : [ ],
"rules" : [ ]
},
"tags" : [ ],
"remoteFactsLoaderRef" : null
}'

and finally let's create a route that uses the client_credentials plugin

curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic xxxx' \
"http://otoroshi-api.oto.tools:8080/apis/proxy.otoroshi.io/v1/routes" \
-d '{
"_loc" : {
"tenant" : "default",
"teams" : [ "default" ]
},
"id" : "4874704c-56a2-4460-9a21-ff8055a19c75",
"name" : "test route",
"description" : "test route",
"tags" : [ ],
"metadata" : { },
"enabled" : true,
"groups" : [ "default" ],
"bound_listeners" : [ ],
"frontend" : {
"domains" : [ "test.oto.tools/token" ],
"strip_path" : true,
"exact" : false,
"headers" : { },
"query" : { },
"methods" : [ ]
},
"backend" : {
"targets" : [ {
"id" : "www.otoroshi.io",
"hostname" : "www.otoroshi.io",
"port" : 443,
"tls" : true,
"weight" : 1,
"predicate" : {
"type" : "AlwaysMatch"
},
"protocol" : "HTTP/1.1",
"ip_address" : null,
"tls_config" : {
"certs" : [ ],
"trusted_certs" : [ ],
"enabled" : false,
"loose" : false,
"trust_all" : false
}
} ],
"root" : "/",
"rewrite" : false,
"load_balancing" : {
"type" : "RoundRobin"
}
},
"backend_ref" : null,
"plugins" : [ {
"enabled" : true,
"debug" : false,
"plugin" : "cp:otoroshi_plugins.com.cloud.apim.otoroshi.extensions.biscuit.plugins.ClientCredentialBiscuitTokenEndpoint",
"include" : [ ],
"exclude" : [ ],
"config" : {
"expiration" : 21600000,
"forge_ref" : "biscuit-forge_dev_7580094c-47e0-495e-80fc-b9c9e8fb8129"
},
"bound_listeners" : [ ]
} ]
}'

now we can call this route to get an access_token

curl -X POST -H 'Content-Type: application/json' "http://test.oto.tools:8080/token" -d '{
"grant_type": "client_credentials",
"client_id": "apikey_client_id",
"client_secret": "apikey_client_secret",
"bearer_kind": "biscuit",
"aud": "http://test.oto.tools:8080"
}'

the result of this call might look something like

{
"access_token" : "EosCCqABCgljbGllbnRfaWQKEHdWc0R3bTJYWmxWd0RCQjkKC2NsaWVudF9uYW1lCgR0ZXN0CgNhdWQKG2h0dHA6Ly90ZXN0Lm90by50b29sczo1MDQ1MxgDIgoKCAiACBIDGIEIIgoKCAiCCBIDGIMIIgoKCAiECBIDGIUIMiYKJAoCCBsSBggFEgIIBRoWCgQKAggFCggKBiCi9ei8BgoEGgIIAhIkCAASIO_5F8o1lXRmahr6IPCxyW1X6Mu1Xsk_AsXtNYEySbTLGkDS_usafk7IFXbiXHwJao7_dFt_6CLB6k6dyK56PHP6Pbl-O9Jn3TxbYT4KNVgIW6DAjkHiisM8sB1YSXeTYqAFIiIKINaTmcs4QrNLiGZ45qvOn_ov589DIwNSfLhAeyiWj9bB",
"token_type" : "Bearer",
"expires_in" : 21600
}

Setup API route

let's create a biscuit verifier that check if the biscuit token is valid and issued for the right domain with the right apikey

curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic xxxx' \
"http://otoroshi-api.oto.tools:8080/apis/biscuit.extensions.cloud-apim.com/v1/biscuit-verifiers" \
-d '{
"enabled" : true,
"id" : "biscuit-verifier_dev_1808df91-1878-4be8-9510-c5ea19098852",
"keypair_ref" : "biscuit-keypair_dev_d25612c6-b4d0-43ed-a711-16b6c09a5155",
"name" : "New biscuit verifier",
"description" : "New biscuit verifier",
"metadata" : { },
"strict" : true,
"tags" : [ ],
"config" : {
"checks" : [ "check if client_id(\"apikey_client_id\")", "check if aud(\"http://test.oto.tools:8080\")" ],
"facts" : [ ],
"resources" : [ ],
"rules" : [ ],
"policies" : [ "allow if true" ],
"revokedIds" : [ ]
}
}'

then let's create the route with the verifier

curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic xxxx' \
"http://otoroshi-api.oto.tools:8080/apis/proxy.otoroshi.io/v1/routes" \
-d '{
"_loc" : {
"tenant" : "default",
"teams" : [ "default" ]
},
"id" : "bfda6474-0f81-4880-966e-8dae5c2683de",
"name" : "test route",
"description" : "test route",
"tags" : [ ],
"metadata" : { },
"enabled" : true,
"groups" : [ "default" ],
"bound_listeners" : [ ],
"frontend" : {
"domains" : [ "test.oto.tools/api" ],
"strip_path" : false,
"exact" : false,
"headers" : { },
"query" : { },
"methods" : [ ]
},
"backend" : {
"targets" : [ {
"id" : "mirror.otoroshi.io",
"hostname" : "mirror.otoroshi.io",
"port" : 443,
"tls" : true,
"weight" : 1,
"predicate" : {
"type" : "AlwaysMatch"
},
"protocol" : "HTTP/1.1",
"ip_address" : null,
"tls_config" : {
"certs" : [ ],
"trusted_certs" : [ ],
"enabled" : false,
"loose" : false,
"trust_all" : false
}
} ],
"root" : "/",
"rewrite" : false,
"load_balancing" : {
"type" : "RoundRobin"
},
"health_check" : null
},
"backend_ref" : null,
"plugins" : [ {
"enabled" : true,
"debug" : false,
"plugin" : "cp:otoroshi_plugins.com.cloud.apim.otoroshi.extensions.biscuit.plugins.BiscuitTokenValidator",
"include" : [ ],
"exclude" : [ ],
"config" : {
"verifier_ref" : "biscuit-verifier_dev_1808df91-1878-4be8-9510-c5ea19098852"
},
"bound_listeners" : [ ]
}, {
"enabled": true,
"debug": false,
"plugin": "cp:otoroshi.next.plugins.OverrideHost",
"include": [],
"exclude": [],
"config": {},
"bound_listeners": [],
"plugin_index": {
"transform_request": 1
}
} ]
}'

and now let call the route with the biscuit access_token

curl "http://test.oto.tools:8080/api" \
-H 'Authorization: Bearer EosCCqABCgljbGllbnRfaWQKEHdWc0R3bTJYWmxWd0RCQjkKC2NsaWVudF9uYW1lCgR0ZXN0CgNhdWQKG2h0dHA6Ly90ZXN0Lm90by50b29sczo1MDQ1MxgDIgoKCAiACBIDGIEIIgoKCAiCCBIDGIMIIgoKCAiECBIDGIUIMiYKJAoCCBsSBggFEgIIBRoWCgQKAggFCggKBiCi9ei8BgoEGgIIAhIkCAASIO_5F8o1lXRmahr6IPCxyW1X6Mu1Xsk_AsXtNYEySbTLGkDS_usafk7IFXbiXHwJao7_dFt_6CLB6k6dyK56PHP6Pbl-O9Jn3TxbYT4KNVgIW6DAjkHiisM8sB1YSXeTYqAFIiIKINaTmcs4QrNLiGZ45qvOn_ov589DIwNSfLhAeyiWj9bB'

{
"method" : "GET",
"path" : "/api",
"headers" : {
"host" : "mirror.otoroshi.io",
"accept" : "*/*",
"user-agent" : "AHC/2.1",
"authorization" : "Bearer EosCCqABCgljbGllbnRfaWQKEHdWc0R3bTJYWmxWd0RCQjkKC2NsaWVudF9uYW1lCgR0ZXN0CgNhdWQKG2h0dHA6Ly90ZXN0Lm90by50b29sczo1MDQ1MxgDIgoKCAiACBIDGIEIIgoKCAiCCBIDGIMIIgoKCAiECBIDGIUIMiYKJAoCCBsSBggFEgIIBRoWCgQKAggFCggKBiCi9ei8BgoEGgIIAhIkCAASIO_5F8o1lXRmahr6IPCxyW1X6Mu1Xsk_AsXtNYEySbTLGkDS_usafk7IFXbiXHwJao7_dFt_6CLB6k6dyK56PHP6Pbl-O9Jn3TxbYT4KNVgIW6DAjkHiisM8sB1YSXeTYqAFIiIKINaTmcs4QrNLiGZ45qvOn_ov589DIwNSfLhAeyiWj9bB",
"x-forwarded-for" : "45.80.20.1",
"forwarded" : "proto=https;for=45.80.20.1:51029;by=91.208.207.223",
"x-forwarded-port" : "443",
"x-forwarded-proto" : "https",
"sozu-id" : "01JJRK7TERSK9776XHKM7590BS"
},
"body" : null
}