I deployed a 3rd party webapp which uses basic authentication for access on Google Cloud Run. I additionally wanted to protect the endpoint by allowing only Google-authenticated users of the organization access. Both these methods use the Authorization header of the request and I cannot get it to work.
I tried following this post, providing both credentials in one field. My reasoning was, that GCP should select the strongest credential mechanism it supports - bearer - and leave the basic credentials for the webap. I have no idea if the webapp could have dealt with it because Google's reverse proxy already barred my access.
curl "-HAuthorization: bearer ${bearer_token}, basic ${base64_userpw}" https://my-google-endpoint.com
-> 401 Unauthorized
I also tried Proxy-Authorization with no different result.
curl "-HProxy-Authorization: bearer ${bearer_token}" "-HAuthorization: basic ${base64_userpw}" https://my-google-endpoint.com
Is there a way to get nested authentication to work with Google's reverse proxy? I was able to get past the reversed proxy by only supplying the bearer but naturally hit the wall at the service. With deactivated authentication on proxy side I was able to authenticate with the service using the basic credentials.
P.S.:
I am not using a browser to access the webapp but command line tools.
You cannot mix Authorization mechanisms with IAP. Everything after the bearer keyword is considered the token string.
One solution is to change your Basic Authorization HTTP header from using Authorization to a custom HTTP header. Something like X-MyApp-Authorization. Then your app processes the custom header to handle the Basic Authorization mechanism.
[Update 2021-08-17]
My original answer is partially wrong. Google's solution is currently broken.
Cloud Run is behind Google Cloud IAP. The client making a request can use two HTTP Authorization headers:
Authorization: <application authorization token>
Proxy-Authorization: Bearer <IDENTITY_TOKEN>
If a valid ID token is found in a Proxy-Authorization header, IAP authorizes the request with it. After authorizing the request, IAP passes the Authorization header to your application without processing the content.
Authenticating from Proxy-Authorization Header
This means the OP was on the right track using the Proxy-Authorization header. However, this feature does not work.
Create an Identity Token:
Use curl to verify that the token works with a Cloud Run endpoint that requires the Invoker role:
curl -H "Authorization: Bearer $TOKEN" $endpoint
That works. Now try the Proxy-Authorization header:
curl -H "Proxy-Authorization: Bearer $TOKEN" $endpoint
That fails with a 403.
Now try both headers:
curl -H "Proxy-Authorization: Bearer $TOKEN" -H "Authorization: Bearer $ANOTHER_TOKEN" $endpoint
That fails with 401 "The access token could not be verified"
gcloud auth print-identity-token
I am using documented methods to use two authorization headers, but this feature does not work.
The PHP SDK did not have the proxy-authorization header support added until June 25, 2021. I created a test application from Google's example. That also failed with the same errors.
June 25, 2021 Patch
Does it happen to work if you send two Authorization headers, like curl -H "Authorization: bearer foo" -H "Authorization: basic bar" ?
--Matthew, Google Cloud IAP engineering
Related
We are testing putting our API behind Google Cloud Platform's IAP. The API itself requires a JWT bearer token in the 'Authorization' header. In such a case, IAP also requires for the appropriate Google-issued token to be in the 'Proxy-Authorization' header.
This works using curl, but I have not been successful in configuring postman to do the same. It seems that in any case the IAP receives the 'Authorization' header first and rejects the inappropriate token.
The following curl command is successful when the appropriate credentials are provided:
curl -X POST 'https://<endpoint>' \
--header 'Proxy-Authorization: Bearer <Google_Auth_Token>' \
--header 'Authorization: Bearer <API_Token>' \
--header 'Content-Type: application/json' \
--data-raw '<Query_JSON>'
However, none of the below seems to work in postman unless there is some mistake I am not aware of.
With Type set to No Auth in the Authorization tab, add the appropriate Proxy-Authorization and Authorization (in that order) keys & values in the headers tab.
Using only Proxy-Authorization results in "Invalid IAP credentials: empty token"
Using the appropriate IAP token in the Authorization header gets past IAP but is then of course rejected by the API.
Combining one bearer token in the Authorization tab and another as a header in the header tab.
Invalid token error when using the IAP token in the Proxy-Authorization header
Get past IAP if using the appropriate token as the bearer token in the Authorization tab
I am using wso2 API manager 3.1.0
I am using REST APIs for creating the subscriptions for the application. I am using the REST API
https://x.x.x.x:9443/apis/api/am/store/v1.0/subscriptions
I am using basic auth for the authentication of the API. My request body is as follows.
{
"applicationId": "6451faaa-65aa-48dc-8655-2ffb623fc441",
"apiId": "ed94f936-7e8a-4c62-92e6-92991aca4348",
"throttlingPolicy": "Unlimited"
}
When I send this request I am getting the 200 OK response but the content of the 200 OK is the management console login page. This is one issue I am facing. Another issue is a very basic one- I am trying to get the applications created in the system for that I am using below API
https://x.x.x.x:9443/api/am/store/v1.0/applications?limit=25&offset=0
again with basic authorization. for this request I am getting the 404 response. Same response I get if I change the authentication to Oauth token.
I tried these APIs with multiple tenant users credentials but for all I am getting the same response.
UPDATE:
I have obtained the access token like below:
1. curl --insecure -X POST -H "Authorization: Basic YWRtaW46YWRtaW4=" -H "Content-Type: application/json" -d #payload.json https://localhost:9443/client-registration/v0.16/register
I got the response
{"clientId":"MPPomfxrQ_ZReRlHz0x70pU6yLca","clientName":"rest_api_store","callBackURL":"www.google.lk","clientSecret":"dQrYXtwMG8fB4Qhu7NfC1uqLAkwa","isSaasApplication":true,"appOwner":"admin","jsonString":"{\"grant_types\":\"password refresh_token\",\"redirect_uris\":\"www.google.lk\",\"client_name\":\"rest_api_store\"}","jsonAppAttribute":"{}","tokenType":null}
2. curl -k -d "grant_type=password&username=admin&password=admin&scope=apim:subscribe" -H "Authorization: Basic TVBQb21meHJRX1pSZVJsSHoweDcwcFU2eUxjYTpkUXJZWHR3TUc4ZkI0UWh1N05mQzF1cUxBa3dh" https://10.57.8.36:9443/oauth2/token
and I got the response
{"access_token":"194d3a3c-8f37-3459-a909-f1f871f096dc","refresh_token":"be7bfcb5-970d-3a8b-8f0a-d69fb7ea53fb","scope":"apim:subscribe","token_type":"Bearer","expires_in":3600}
using the obtained token if I place the API request I get still 404
As per google's docs, I'm generating my oauth access token like this:
export TOKEN=$(~/go/bin/oauth2l fetch -jwt -json ~/.google/my-service-key.json cloud-platform)
I'm then doing requests to Google's REST API like this:
curl -v -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d (...) $URL
The response I'm getting back from Google is that I'm not providing an OAuth token, when I clearly am:
Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.
Other posts suggest to use gcloud auth application-default print-access-token instead of the OAuth token, but I know this to be the incorrect approach, as Google's API responds back that it wants a service account OAuth token and not an identity.
Any idea what's happening here?
Sometimes an old (bad) token gets cached from before you rotated the service_account.json.
Delete Cache
Try this:
rm ~/.oauth2l
Token vs JWT
And try getting an API token before you sign the JWT:
oauth2l fetch cloud-platform
Scope vs Audience
Also, the API token requires a scope (shown above), whereas the JWT requires an audience aud, which is a URL:
oauth2l fetch --jwt https://www.googleapis.com/auth/cloud-platform
ENVs
You may also want to make sure that you don't have competing configuration, see if GOOGLE_APPLICATION_CREDENTIALS is set.
echo $GOOGLE_APPLICATION_CREDENTIALS
unset GOOGLE_APPLICATION_CREDENTIALS
Or potentially use it instead of --json ./service_account.json:
export GOOGLE_APPLICATION_CREDENTIALS=$HOME/.config/google/service_account.json
HTH
I have successfully configured the API Manager, ID and IS according to the documentation: https://docs.wso2.com/display/AM260/JWT+Grant#JWTGrant-UsingtheJWTgrant.
I invoke the WSO2 token endpoint to exchange an external JWT for a WSO2 access token:
curl -i -X POST -k -d 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6WyJ3c28yIl0sImVudmlyb25tZW50Ijp7InByb2ZpbGVzIjpbImRldiJdfSwiYXV0aCI6eyJyb2xlcyI6WyJVU0VSIiwiQURNSU4iXX0sInVzZXJfbmFtZSI6IjAwMDAwMDk5Iiwic2NvcGUiOlsib3BlcmF0ZSJdLCJpc3MiOiJQQVMiLCJleHAiOjE1NDUzNDgyODcsImdlbmVyYXRlZEJ5IjoiUEFTIiwianRpIjoiOWQ4ZWU3ZTgtNDBlZS00MTZjLTlkYjgtYjU2NDZhYTZhN2JmIiwiY2xpZW50X2lkIjoiZnJvbnQtcG9saXphcyJ9.Ccs1OxjteRsvHTump-ZTawEsqlTrIeO0LJUzt5Ita8udvMOa_tB1rHOtI8GAa2mDCPMD_Z_jtZ2SlXPs10GvsYlF4jS_wcCVAPtHsoigzuNtg5t7CVfeCI2Bzhak721LdYBcjB9s0Jn24G9eb2jqx8NF0RPlKgmhbxwdY0b8XeigLp-kGCsFKY_fDIjFUM0oifzCWOmtaCRMtMx3CKVZOWq9dBIokheCi2foL8YkBCz57yo4vb782AYWXdiHj38TPPe4IguARuoc9FSymyiL1gWHJmyMZFvAeAJkDnHHEnnezqPmcWQweC1ylLwUYGNVLM8YSfuBDtcGBWSO0F-WKw' -H 'Content-Type: application/x-www-form-urlencoded' https://localhost:9443/oauth2/token -d 'client_id=w_paekjnDDY8zcCfCRgj_81g2eYa'
This answers successfully with an access token, a refresh token etc etc.
I have created an application in the WSO2 APIM store. In the production/sandbox tabs, the only checked Grant Type item is JWT.
The point is, I use the previously gotten access token (which is itself an JWT token) to invoke an API subscribed with the above application:
curl -k -X GET "https://192.168.179.129:8243/myapp/api/v1/customers" -H "accept: application/json;charset=UTF-8" -H "Authorization: Bearer eyJ4NXQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJraWQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6IndfcGFla2puRERZOHpjQ2ZDUmdqXzgxZzJlWWEiLCJuYmYiOjE1NDUzNDgxOTgsImF6cCI6IndfcGFla2puRERZOHpjQ2ZDUmdqXzgxZzJlWWEiLCJzY29wZSI6ImRlZmF1bHQiLCJpc3MiOiJodHRwczpcL1wvbG9jYWxob3N0Ojk0NDNcL29hdXRoMlwvdG9rZW4iLCJleHAiOjE1NDUzNDgyODcsImlhdCI6MTU0NTM0ODE5OCwianRpIjoiNjRlM2I5N2UtOTNlNC00YzQ2LThlNmQtMzlmZjQzOWQxM2Y0In0.UBLOsCCD3t4Wf8nXBnDkkGXxefYySelDzEcs1F_IrbshMJXohxcL92Av1nmcpdNdjin7GdC8Y305rrkBt9T1L_cMAHLYYcI5cI1J7wmAgEd1CEv9gI7IUYfAdbga2AeV4kIlNsgiV6PKnU34WnY7rEVqXD908eEHY5UvaNXc0Bz6C8d-p39-SqKUblGHPh9vdkpcCGcK0CgGKjtiU2lai_JkRALdgEgonT37R5eqmuxPxUouWNz9TCJgTuonKPA-9bYOsMvbzGlm--0m0j9gdxnv-3N1Kv_2JqSCR4pToDClhSKgFCE1L025LIICM-sLd_PDU5pwYge_iKseiIDZfA" -d 'client_id=w_paekjnDDY8zcCfCRgj_81g2eYa'
I get the following error (900908) - Resource forbidden:
<ams:fault xmlns:ams="http://wso2.org/apimanager/security"><ams:code>900908</ams:code><ams:message>Resource forbidden </ams:message>
<ams:description>Access failure for API: /myapp/api/v1, version: v1 status: (900908) - Resource forbidden </ams:description></ams:fault>%
I must be missing the final step which is how to allow those access tokens gotten in the JWT grant to be used to access an API subscribed by an application.
The error code 900908 means the API is not subscribed by the application. Please double check.
Technically Bee was right in his answer, but I would like to point specifically at what I was doing wrong in case it happens to others:
The problem was that the client_id/client_secret I was using when exchanging the JWT to get the access token were the ones from the Service Provider I had created. WRONG!
The ones was that need to be sent are those from the subscribed application. With that the resource forbidden error doesn't show up anymore.
I tried to access Google's Datastore through their REST Api. It says that they allow authentication through the API-key. However it doesn't seems that I can get it to work any where. I copied the snippet generated from their Try this API page.
curl --request POST \
'https://datastore.googleapis.com/v1/projects/PROJECT_ID:runQuery?key=[YOUR_API_KEY]' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{"query":{"filter":{"compositeFilter":{"op":"AND","filters":[{"propertyFilter":{"property":{"name":"id"},"op":"EQUAL","value":{"stringValue":"ID"}}}]}},"kind":[{"name":"NAME"}]},"partitionId":{"namespaceId":"NAMESPACE_ID","projectId":"PROJECT_ID"}}' \
--compressed
But it keeps returning me an 401 error.
{
"error": {
"code": 401,
"message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
}
It looks like it require me to use OAuth instead, which is not what their documentation says. Anyone experienced something similar?
You are using an API key which is incorrect.
This link details which services support API Keys. Cloud Datastore is not one of them.
Using API Keys
You want to use an Access Token which is derived from Service Account credentials.
Review this document.
Using OAuth 2.0
The steps to generate an Access Token:
Load the service account credentials json file.
Extract the client_email, private_key and private_key_id.
Create a json payload.
Call the authorization URL: https://www.googleapis.com/oauth2/v4/token
This returns a json object. Extract the access_token.
Use the access_token instead of an API Key.
There are examples on the Internet in various languages. The link will get you started. The process appears complicated, and it is, but once you understand it, generating Access Tokens is easy and they can be reused until they expire (typically 60 minutes which you control).
This document on Google Cloud Storage authentication is the same for Cloud Datastore. The key is understanding "Authorization: Bearer" which is a header you need to include with your curl request.
Authentication