I have setup a Cognito user pool so that I can use it to authorize access the an api gateway. It uses OAUTH2 and the flow im using is : Authorization Code Grant,
Scopes : email, openid and profile,
Allowed Custom Scope : product-api/read_product, product-api/create_product, product-api/delete_product
I use boto3 admin_initiate_auth command to connect to the user pool:-
response = idpclient.admin_initiate_auth(
UserPoolId=USERPOOLID,
AuthFlow='ADMIN_NO_SRP_AUTH',
AuthParameters={
'USERNAME':USERNAME,
'PASSWORD':PASSWORD,
'SECRET_HASH':SECRET_HASH
},
ClientId=APPCLIENTID
)
and the response I receive is a json object with several fields, which include access_token, refresh_token etc...
but when I use the access_token to access the api gateway, i get a 401 error. Unauthorised. Looking into the access_token it looks like the custom scopes have not been added.
Could you advise why the custom scope has not been added to the access_token and how do i get the custom scopes added ?
the api gateway has a lambda authorizer added.
Usually you have to specify the Scopes in 2 places:
The OAuth client entry for the client application in the Cognito section of the AWS console
The code requesting a token - I have always implemented this in a standards based manner whereas you are using an AWS specific solution
Looks like what you want may not be supported via admin_initiate_oauth:
Include user details in AWS Cognito Oauth2 token
If your client application is a web UI then the standards based solution will do what you want.
I've tested my Cognito single page app sample with custom scopes - you can run it here:
https://authguidance.com/home/code-samples-quickstart
Not sure if this type of solution will work for you though ..
Related
I used Serverless to create API gateway endpoints + lambda functions. I want to secure API gateway endpoints using the access token oAuth. I am trying to provide credentials (sign up and sign in) in my iOS native app. Here are the facts I learned.
I want to secure my API gateway using OAuth and access token.
Because I have already created API gateway endpoints and lambda functions, Signup and Sign in using Amplify is not the best idea. I can't use Amplify's access token in the API gateway because in the Cognito user pool (under App Client Settings) I have selected Authorization Code grant (instead of implicit which is not that secure and client credentials are for the machine to machine) type OAuth flow.
I have in app Sign up and Sign in UI and don't want to use Hosted UI.
I want to authenticate and authorise users of my app.
My question is:- Can I use the Authorization code grant type without using Hosted UI or web browser? i.e. make a rest call with username and password and get the Authorization code. Once I get the Authorization code then make a call to the token endpoint to get the access token. Once I get the access token then call API gate with that access token. All of the above natively without using a browser or hosted UI.
Thank you very much in advance.
I am using Cognito and ADFS integration for one of my web app. After the integration, I get the tokens correctly from Cognito. However, when I try to retrieve user's attributes using Auth.userAttributes(), I get a 400 error code with following error:
{__type: "NotAuthorizedException", message: "Access Token does not
have required scopes"} message: "Access Token does not have required
scopes"
__type: "NotAuthorizedException"
I decoded the received access token on jwt.io and it correctly has the openid and profile scopes
"scope": "openid profile email"
As per the Auth standards, I expect that these scopes should be enough to READ a basic user profile. Is this not a correct expectation ?
P.S: I tried asking for a token with aws.cognito.signin.user.admin scope and this seems to make the user attribute call without error. However, why is this required. I don't want to expose to the user any access token to modify any information
In my own experience with Cognito access tokens, you are unable to add custom claims to them. This has some good and bad points:
Pros
Tokens used by web and mobile apps do not reveal sensitive data
Cons
Tokens alone do not enable APIs to do their authorization properly
You can get the details you need by sending the access token to the User Info endpoint though. It is possible to do this when an access token is first received, then cache custom claims for future requests with the same access token:
Getting user info claims
Creating a custom claims principal
You can run the above code sample by following its README. The main point of the API security code is to form a useful claims principal that the API can use for its business authorization.
I believe you are using aws amplify for this task.
As you are using federated sign in to authenticate user, you will get user attributes in the following way:
const user = await Auth.currentAuthenticatedUser();
const {yourAttributeName} = user.signInUserSession.idToken.payload;
Though you are not using cognito user pool user for this task, but you can access their user attributes like the following way:
const {yourAttributeName} = (await Auth.currentAuthenticatedUser()).attributes;
If the above solution doesn't work, Make sure your Scopes are properly setup in your userpool app and Identify Provider have a proper attribute mapping setup and have read-write permission on the attributes that you are expecting to read.
I've read docs and seen this. Struggling to put Cognito + API GW + OAuth2 pieces together. My questions:
Do I correctly understand the flow and use of Resource server scopes: client app asks the Cognito user pool for a JWT token (login/authorization happens). Request for a token contains custom scope A so as the Cognito returned JWT access token. After that client app uses obtained token making a REST API call to a "resource server" (say, to our configured API GW endpoint). API GW endpoint is set to use our Cognito user pool as authorizer + scope is set to be custom scope A. Thus scope here acts like a "role" or "permission": if client has a valid JWT token + this token has a custom scope A inside + API GW endpoint is set to use that scope - then client app is authorized to call API GW endpoint. Effectively it acts like a "resource-based IAM policy" for endpoint but no IAM is involved here.
Do I correctly understand that AWS Cognito Resource server identifier is an arbitrary string? It is not the URI of a factual "resource server" (our API GW). URI format is used purely for uniqueness and there is no place in flow where Cognito Resource server identifier matters or somehow checked/validated? Also it looks like that Resource server identifier does not affect JWT token generation or token contents?
thanks for clarif.
Application scope should not be confused with user permission. Scopes define the access an application has to the user's resources. Therefore resource access is the overlap of the two:
Check that the user has access
Check that the application has scope (access to user acccess)
Example
2 Clients with scopes:
A: E-Commerce (product:create, product:remove, order:create, order:update-status, order:read-status)
B: 3rd-party Order Tracker (order:read-status)
2 Users with permissions:
Customer (order:create:owned, order:read-status:owned)
Admin (product:all, order:all)
Therefore…
Customer PUT /product with client A = DENY due to missing user permission
Admin PUT /product with client B = DENY due to missing scope
…
By requiring a URI, AWS seems to enforce collision resistant identifiers as you point out. It's not a common practice and doesn't really have any real security benefit, nor is it validated by AWS that you control the resource.
I am having trouble understanding how to accomplish this. I have Firebase functions running on my application. I am using an external API in which I can configure Webhooks to hit an endpoint on my Firebase functions to perform an action. To make sure that the call comes from this external API, they recommend using an oauth2 flow. Mainly they ask me for:
Provide us (the external API) with an ID and an access token;
these are used to access a URL which provides a bearer token;
this bearer token is then used to access the provided webhook URL until the bearer
token expires after a pre-determined period of time.
And there are 4 input fields:
1. OAuth2 access token url
2. OAuth2 client id
3. OAuth client secret
4. OAuth2 Scope. <---- NOT SURE WHAT THIS ONE MEANS
My question is how do I generate the access token and the client id for this external API?
What value should I put for the oAuth2 scope?
Thanks!
I was able to figure this out using auth0. In one of their documentations, they cleared explained what I was trying to accomplish. Posting here to future reference in case any one needs it.
Thanks all!
reference: https://auth0.com/docs/authorization/flows/client-credentials-flow#learn-more
You can generate the client ID and client secret in the Console > Credentials.
Cloud Functions API oAuth2 scope is https://www.googleapis.com/auth/cloud-platform.
I have a cloud function I want to call from a rails app that is currently running on Heroku. I have a service account set up for the rails app, and the could function is deployed and triggered by HTTP request. I want to limit invocations of the cloud function to the rails app, and was planning to use server-to-server 2-legged OAuth 2.0 following this documentation from google.
When I'm setting up the service account though, I need to supply a scope. According to the list of available scopes the only scope referring to Cloud Functions allows management of the cloud function, whereas I want a scope to cover invocation only.
The language used in the docs refers to scopes being used to define access to Google APIs.
Does my cloud function qualify as a google API in this sense?
Otherwise, since scopes are URLs, could I just supply the URL of my cloud function as a scope?
Right, I managed to cobble the process together from several disparate Google documentation pages, namely
https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_a_service_account
incorrectly instructs you to self-sign your JWT, which obviously won't work because then anyone could sign their own JWT and access your cloud function
https://cloud.google.com/functions/docs/securing/authenticating#service-to-function
this example doesn't tell you what audiences to set in your initial JWT claim
https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests
incorrectly describes the JWT claim contents and signing process
the alternate method of using service account signed JWTs directly as bearer tokens doesn't work for calling cloud function, I'm guessing because they aren't google APIs
Here's what worked for me
Marshal a JWT with the following data. The main trick was to set aud to the OAuth endpoint, set sub to the service account's email (even though there's no user to impersonate), and target_audience to the Cloud Function's URL (per doc #1), but follow the general process in doc #3.
{
"aud": "https://www.googleapis.com/oauth2/v4/token",
"sub": "<your service account's email style identifier>",
"iss": "<your service account's email style identifier>",
"target_audience": "<the URL of the cloud function you're trying to call>",
"exp": <unix epoch timestamp + 3600 as an integer>,
"iat": <unix epoch timestamp as an integer>
}
Base64 encode the claim. Base64 encode a claim header set ({"alg":"RS256","typ":"JWT"}) and combine the results with a period, header.claim, per generic instructions in doc #3. You should get something like this: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjkt.... Contrary to doc #3, you don't need kid in the headers.
Use the service account private key to sign the JWT with your favourite JWT library
Exchange your service account signed JWT for an access token from Google's OAuth system, by making POST call to https://www.googleapis.com/oauth2/v4/token with an Authorization: Bearer <signed JWT base64 encoded string> and the following two fields form data encoded:
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
assertion: <signed JWT base64 encoded string, YES you use it twice>
If all goes well, you should receive a JSON response containing a key id_token, which you can use in an Authorization: Bearer <id_token> when you call your CloudFunction
The Cloud Functions documentation regarding this topic covers several use cases:
Securing developer access so that only specific users can invoke the
function during testing.
Securing function-to-function access so that only authorized functions
can invoke your function.
Securing end-user access to an application from mobile or web clients.
Your use case is the third one, the end-user authentication is integrated through Google Sign-In and Firebase Authentication but this won't be useful for restricting HTTP calls to allow only your Heroku App.
At this point the workaround should be managed by checking in your Cloud Functions code whether the request comes from the Heroku App and reject those which doesn't.