Express Gateway adding scopes to endpoints - express-gateway

I have a working autoregister gateway function, which fills my gateway-config.yml.
We will be using scopes so I added scopes to postData, but it doesn't add scopes.
const postData = JSON.stringify({
host: '*',
path: currentKey,
methods: methodsByPaths.get(currentKey),
scopes:
Scopes[`${process.env.ENDPOINT}${currentKey.split('/').join('')}`],
});
Some console log of currentKey and the Scopes[...]
/api/users - undefined
/api/token - undefined
/api/createProject - PROJECT-MANAGER
Also, when the scope is undefined, it adds the endpoint, if it is not undefined (project-manager) it didn't even add the endpoint. I tried hard codding scopes to something and it still didn't work

You shouldn’t send scopes in the POST request; they are strictly internal to Express Gateway.
When a user is authenticated by Express Gateway, the authentication credential defined in Express’s config.yml file the user is authenticated against should include the list of scopes the user has access to; these are used internally by Express Gateway to control access to endpoints.

Related

How To Invoke Cloud Functions With Workspace Users?

I am trying to invoke GCP functions through my angular app hosted on App Engine. I cannot seem to find any straightforward answer given my users are not signed up through GCP but through Google Workspace. So in short the app I am building is only for internal users. As of right now I am able to log in fine using Google Authentication, the problem is that after I log in, the cloud function is rejecting my request. I have included all the steps that I've taken along with the error I am receiving from the cloud function.
So heres what I've done so far.
I implemented the login button inside of my Angular app using
angularx-social-login.
I then obtained an OAuth 2.0 Client ID from
the GCP project in which the functions are hosted (as this is the
same project anyway).
After this I registered the OAuth consent screen and set it to internal as I don't
want anyone but my internal workspace users to be able to access this
I then went to users identity platform and registered the same OAuth
2.0 client ID that I spoke of in step 2.
I then set up the GCP function to allow allAuthenticatedUsers (I've tried many other
roles but I would accept if I could just get allAuthenticatedUsers to work for now)
Finally back in my angular app I passed into the function call
headers the idToken that I get each time a user logs in using the
Google Login Popup
My code looks like:
DashboardComponent.ts
import {SocialAuthService} from 'angularx-social-login';
...
this.authService.authState.subscribe((user) => {
this.myService.callFunction(user.idToken).subscribe((userRes) => {
...
}
...
FirebaseFunctionService.ts
callFunction(authToken): Observable<any> {
const headers= new HttpHeaders()
.set('content-type', 'application/json')
.set('Access-Control-Allow-Origin', '*')
.set('Authorization', `Bearer ${authToken}`);
return this.http.get('https://my-cloud-function-url/my-function', { headers: headers
});
}
And the response that I get when I call this function is:
error: ProgressEvent {isTrusted: true, lengthComputable: false,
loaded: 0, total: 0, type: 'error', …} headers: HttpHeaders
{normalizedNames: Map(0), lazyUpdate: null, headers: Map(0)} message:
"Http failure response for
https://myFunction.cloudfunctions.net/myFunction: 0 Unknown Error"
name: "HttpErrorResponse" ok: false status: 0 statusText: "Unknown
Error" url: "https://myFunction.cloudfunctions.net/myFunction"
[[Prototype]]: HttpResponseBase
Does anyone know why this might be happening? Any help would be greatly appreciated as I am at my wits end. Thanks in advance :)
Status code 0 in the error message indicates a CORS failure. You can go through this GitHub issue comment, where the stackoverflow thread points to a number of reasons for this error.
Also you need to write this line of code in your initialisations :
const cors = require('cors')({origin: true}) and check out Google’s documentation on how to handle CORS requests. Also you have to provide proper permissions - one of the important ones being giving Cloud Functions invoker role to your cloud functions.
Joe, (author of our question) agreed that it was a CORS error but he solved it by giving allUsers permission (making the function public) and verifying the user in the function itself and the CORS disappeared.
Now the reason behind this :
I think there was some issue with Joe’s authentication mechanism and hence functions were not authenticated. HTTP functions require authentication by default. And as it did not have it, as per documentation the workaround was to make Joe’s function public by setting the --allow-unauthenticated flag, or use the Console to grant the Cloud Functions Invoker role to allUsers. Then handle CORS and authentication in the function code (which he was doing as he mentioned).
So when Joe made the function public by granting allUsers and CORS was handled in code, it started working and the CORS errors disappeared.

is the Authorization header passed to lambda function by Cognito Authorizer?

I'm planning to add some authorisation logic to my web app. Users are managed & authenticated by Cognito, and the API is powered by Lambda functions glued together with API Gateway.
Currently, Cognito just validates the user's OAuth token and allows/denies the request.
I'd like to further restrict what actions the user can take within my lambda functions by looking at the user's groups.
Looking at the OAuth token, claims about the groups are in the token body. My question is, does the Cognito Authorizer pass the value of the Authorization: Bearer foo header through to API Gateway and the Lambda handler?
The way I can do something like this:
const groups = getGroupsFromToken(event.headers.Authorization);
if (groups.includes('some group')) {
// let user do the thing
}
else {
callback({ statusCode: 401, body: 'You can\'t do the thing' });
}
It definitely sends through the token on a header for me, also it sends through requestContext.authorizer.jwt.claims which may be more useful to you.
The older api gateways I have always uppercase the header to "Authorization", irrespective of what case the actual header uses. The newer ones always lowercase it to "authorization".
I'd suggest trying:
const groups = getGroupsFromToken(event.headers.Authorization || event.headers.authorization);
I am using lambda proxy integration (what the new APIGW UI is calling lambda integration 2.0), from your callback it looks like you are using it too. If you are using the old lambda integration (1.0 in the new UI) then you need a mapping template.

How can I get my Auth0 permissions into the scope claim of the access token for an AWS HTTP API Jwt Authorizer?

I am trying to get an AWS HTTP API JWT Authorizer with scopes on an endpoint to work happily with my Auth0 access tokens.
The JWT Authorizer looks for the necessary scopes in the access token's "scope". I am thinking that this is used for fine-grained authorization. But, Auth0 returns permissions in a "permissions" array rather than in the token's "scope".
Is there a way to get my permissions to show up in the "scope" of the access token so that I can use the JWT Authorizer to handle fine-grained permissions? Or will I need to have my lambda function dissect the authenticated JWT after its gone past the JWT Authorizer?
There is a way to add your user permissions into the scope claim of your token. This thread details two ways to achieve this, thread:
Adding a rule that reads these permissions and copies them into your access_token scope claims.
function (user, context, callback) {
var ManagementClient = require('auth0#2.17.0').ManagementClient;
var management = new ManagementClient({
token: auth0.accessToken,
domain: auth0.domain
});
var params = { id: user.user_id};
management.getUserPermissions(params, function (err, permissions) {
var permissionNames = [];
permissions.forEach(function(obj) { permissionNames.push(obj.permission_name); });
if (err) {
// Handle error.
}
context.accessToken.scope = permissionNames;
callback(null, user, context);
});
}
Using the RBAC feature with TOKEN_DIALECT. Note that information around this is extremely scarce, this post linked below it the only piece of information I have found about it. Also I could not get this to work consistently so personally I use the method #1 listed here. TOKEN_DIALECT
In an ideal world option #2 is really the best and has the least configuration, but I have had issues with this.
Is there a way to get my permissions to show up in the "scope" of the access token so that I can use the JWT Authorizer to handle fine-grained permissions?
This is a bad idea. A JWT token is small (<8 kb according to this answer). What will happen when you have a million resources? Will your "array of permissions" have a million items, too?
https://auth0.com/blog/on-the-nature-of-oauth2-scopes/
scopes are used to express what an application can do on behalf of a given user. (...) scopes are used for handling delegation scenarios (...) overloading scopes to represent actual privileges assigned to the app (as opposed to the delegated permissions mentioned above) is problematic
I work for Auth0 and we're building a solution to handle fine-grained authorization. See https://zanzibar.academy/

AWS Cognito; unauthorized_client error when hitting /oauth2/token

Steps taken so far:
Set up new user pool in cognito
Generate an app client with no secret; let's call its id user_pool_client_id
Under the user pool client settings for user_pool_client_id check the "Cognito User Pool" box, add https://localhost as a callback and sign out url, check "Authorization Code Grant", "Implicit Grant" and everything under "Allowed OAuth Scopes"
Create a domain name; let's call it user_pool_domain
Create a new user with a username/password
Now, I can successfully go to:
https://{{user_pool_domain}}.auth.us-east-2.amazoncognito.com/oauth2/authorize?response_type=code&client_id={{user_pool_client_id}}&redirect_uri=https%3A%2F%2Flocalhost
This presents me with a login page and I am able to login as my user which returns me to https://localhost/?code={{code_uuid}}
I then try the following:
curl -X POST https://{{user_pool_domain}}.auth.us-east-2.amazoncognito.com/oauth2/token -H 'Content-Type: application/x-www-form-urlencoded' -d 'grant_type=authorization_code&redirect_uri=https%3A%2F%2Flocalhost&code={{code_uuid}}&client_id={{user_pool_client_id}}'
However, this just returns back the following:
{"error":"unauthorized_client"}
The token endpoint docs say that unauthorized_client is because "Client is not allowed for code grant flow or for refreshing tokens." which is confusing because I checked the boxes allowing the client to use the code grant flow.
So, it turns out that the user pool has to have a trailing slash (https://localhost/) and then that trailing slash has to be used in all of the callback URLs. Then it decides to work!
Everything looks OK to me. I think it may be complaining about the Authorization header missing but not sure. You could try a few things:
1) According to this page (https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html), you shouldn't need to send the Authorization header in the token request, but maybe it is still needed. You could try either passing just the client ID in it (Authorization [client ID]) or configure a secret and try passing Authorization [client ID:client secret] like it says). It usually makes sense to use a client secret for authorization code flow anyway since in this flow, there is a server side component that can securely handle the token exchange.
2) Try using Implicit Flow instead to see if that works. Implicit Flow makes sense for single page apps with no server side component. For that, no client secret is needed.
If you are using amplify and have it configured outside of the CLI and the other answers aren't working for you, one last fix you can try is to ensure you have responseType: 'token' if you are using implicit flow. Fixed things for me.
Auth: {
oauth: {
domain : 'your-app.auth.your-region.amazoncognito.com',
redirectSignIn: environment.cognito.oauthCallbackLogin,
redirectSignOut: environment.cognito.oauthCallbackLogout,
responseType: 'token',
scope : ['email', 'openid', 'profile'],
}
}
I had this error for another reason: I had response_type=token in the request URL, but the implicit OAuth flow was not enabled in the user pool client, so I needed to change it to response_type=code.
I agree with #rioastamal and #kiran01bm
as well. I did not need a trailing slash and it has to be verbatim as configured for the callbacks.
In my case I had my Redirect URI encoded at definition like this const redirectUri = encodeURIComponent(REDIRECT_URI).
Later, when it was used in the POST call to the /token endpoint as part of the params, it resulted as a double-encoded string.
A facepalm moment, but could happen to anyone.
Getting rid of one of the encoding fixed it for me.
Make sure to also include the scope in the request. Like the following
https://domain.auth.eu-central-1.amazoncognito.com/signup?client_id={}&response_type=token&scope=aws.cognito.signin.user.admin+email+openid+phone+profile&redirect_uri=https://www.google.com/
I my case, the issue came from the ACS URL that was incorrect, but so close that I did not see it. It was redirecting my to a page with this error "An error was encountered with the requested page."
I configured the UserPoolClient via cloudformation and had the AllowOAuthFlows set to implicit, where to work with amplify/cognito I needed that value to be code.
GoogleUserPoolClient:
Type: AWS::Cognito::UserPoolClient
DependsOn: GoogleUserPoolIdentityProvider
Properties:
UserPoolId:!Ref MyUserPool
AllowedOAuthFlowsUserPoolClient: true
GenerateSecret: false
CallbackURLs:
- http://localhost:8080
LogoutURLs:
- http://localhost:8080
AllowedOAuthFlows:
- code
AllowedOAuthScopes:
- email
- openid
SupportedIdentityProviders:
- Google
Authorization code grant means you get a code at the end of that redirect and you have to exchange that code for the respective tokens, and the response Type will be code.
And the Implicit grant type is the equivalent of response type token, where in you will get the tokens on the first step itself.
So check if you have the correct response type as per your auth flow set in the cognito console.
In my case, I updated the localhost:port in Allowed callback URLs of cognito app client setting but failed to add localhost:port to Allowed sign-out URLs

How to get the accesstoken from alexa after account linking with azure AD

I tried to connect my Alexa AWS Lambda function (node.js 6.10) with Azure Activ Directory to my Azure-Cloud-API. After reading the documentation from amazon and many tutorials I have now a working Account Linking. That means, I can link the Account of the Skill inside the Alexa-App on my smartphone.
AccountLinking for my Custom Skill:
{Data from my azure portal}
Authorization Grant Type: Auth Code Grant
Authorization URI: {OAUTH 2.0 AUTHORIZATION ENDPOINT}
Access Token URI: {OAUTH 2.0 TOKEN ENDPOINT}
Client ID: b9c6[...]bc60 {Application ID}
Client Secret: {Client Secret}
Client Authentication Scheme: Credentials in request body
scope: openid
domain: empty
redirect urls: --> In Azure portal as ALLOWED TOKEN AUDIENCES and Reply URLs defined
In my aws lambda function I get the event request from alexa like the documentation says with properties for version, session, context, request...
My understanding of the documentation is, that the token I need for the Azure-Cloud-API-Request should be here: session.user.accessToken
But this token doesn't look like the one I need and after my test runs I get always "Unauthorized" back.
The Token looks something like this and is 1252 characters long:
AQABAAAAAADX8GCi6Js6SK82TsD2Pb7rqGN56iHT_YSxlSr1RAdXucGs0S3ykOaw0XZ1WnjJotqZAn9BH7agRbP0VQv2rnJuRw_aJil7 [...] JIEO2Ap4wuG-tTwiSmZBfbLhyYtwQmxLAkqiLApqFmBYcyu-dnzlVV4liDGyTQ7gAXufd3zt7QGmi3UfP1aL9f5NBeXbmxnU6FHRzF10QZa19pTQgNTtIK8oIAA
If I configure postman and send a request to the azure activ directory I get a accessToken like this (1168 characters long):
eyJ0eXAiOiJKV1QiLCJhbGc [...] Ezbk5aY2VEYyJ9.eyJhdWQiOiJodHRwczovL21ldGVvcmEtYXBwLmF [...] kY5MWVUUXdBQSIsInZlciI6IjEuMCJ9.KJco47-FdJ_eeqv38LL [...] YK_4JqCRDw
This one looks like a jwt-token and if I copy this token directly in my aws lambda function and use this one for the Azure-Cloud-API-Request it works (until the token expires).
Now I'm not sure if there is a problem in my configuration of the account linking? Or do I have to do something with the token from alexa to get the real one? Or is the real token somewhere else and I have to fetch it there?
Thanks a lot for your help!
Amazon Documentation "Alexa Skills Kit":
https://developer.amazon.com/docs/custom-skills/link-an-alexa-user-with-a-user-in-your-system.html
EDIT (Solution) 11.06.2018
Authorization Grant Type: Auth Code Grant
Authorization URI: {OAUTH 2.0 AUTHORIZATION ENDPOINT} + ?resource= + {Application ID}
Access Token URI: {OAUTH 2.0 TOKEN ENDPOINT}
Client ID: b9c6[...]bc60 {Application ID}
Client Secret: {Client Secret} App>Settings>Keys new Key with expiration date = 2 years
Client Authentication Scheme: Credentials in request body
scope: empty
domain: empty
redirect urls: --> In Azure portal as ALLOWED TOKEN AUDIENCES and Reply URLs defined
It sounds like you haven't completed the account linking sequence for your skill. After setting the account linking configuration you need to open the Alexa app (on your phone or https://alexa.amazon.com) and go to your new skill and link your account. Once that is successful you will get a token in request.Session.User.AccessToken.
The blog post: https://blogs.msdn.microsoft.com/premier_developer/2017/12/09/amazon-alexa-skills-authenticated-by-azure-active-directory-and-backed-by-asp-net-core-2-0-web-api-hosted-on-azure/ needs to be updated with the following:
You can ignore the sections about the "front end" app registration.
In Alexa account linking section update URLs to use login.microsoftonline.com instead of login.windows.net
ClientId to be the Application Id of the "back end" app registration
The ?resource= has to be set and has to be the same as the audience parameter for the JWT bearer options. This is ClientId if you use the .Net Core 2.0 template in Visual Studio.
The client secret (key) that is used can not be one that "Never Expires". Use a 1 or 2 year duration.
In addition to Nate's answer and following the addition of ?resource= to the Authorisation URI, I had to give the API permission Azure Active Directory Graph > User.Read on the App Registration and Grant Admin Consent to it.
Without this permission, the account linking would throw an error. If you have similar issues, try your OAuth values with Postman and check the Postman Console for error messages