I am using AWS Amplify with Appsync. I want to add a public query listproducts and it is giving me the hardest time. Based on my understanding, I know that public access needs to be given using apiKey or iam. The trouble is that my mutation enroll lambda function internally makes a create mutation and it is using iam permissions to create those records. I have enabled allowUnauthenticatedIdentities=true but I keep getting access denied for the listproducts query when using iam permissions. I need to make sure that if I go with the iam method, that I have a way to distinguish between the public iam and the private iam that is doing the administrative work in my enroll call and I don't see a way to dictate whether I'd want to use an authenticated vs unauthenticated iam role.
type Product #aws_iam {
id: ID!
}
type UserCourse
#model
#auth(
rules: [
{ allow: groups, groups: ["Admin"] }
{ allow: owner, operations: [read] }
{ allow: private, provider: iam, operations: [create, read] }
]
) {
id: ID!
courseId: String
invoice: String
}
type Query {
listproducts: [Product]
#function(name: "listproducts-${env}")
#auth(rules: [{ allow: public, provider: iam }])
}
type Mutation {
enroll(courseId: String, priceId: String, source: String): UserCourse
#function(name: "coursesenroll-${env}")
}
What am I missing here? I am connecting using Gatsby and AWSAppSyncClient. Is there some trivial way of doing this that I'm just missing? My ideal world would be that I could run an unauthenticated query with cognito pools and it would return so I didn't have to deal with instantiating different appsyncclient's based on whether I'm logged in or not. I tried manually adding permissions to the unauth role and I'm not sure if that's the right approach but I'd be happy to go this route too if I can't do unauth out of the box with amplify.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"appsync:GraphQL"
],
"Resource": [
"arn:aws:appsync:eu-central-1:SOME_LONG_STRING:apis/SOME_LONG_STRING/types/Query/fields/listproducts"
]
}
]
}
Related
There really isn't enough documentation on this either in the AWS docs or in the Github, so hopefully someone here has tackled a similar issue.
I have a react app with backend api hosted on AWS, using appsync, dynamoDB, and cognito-user-pools. My IAM policies are set up to allow unauth users read-only permission to some public tables. I tried the public api key but that didn't do anything. I'm trying to get the IAM unauth role permissions set up but even when I experimentally added literally every service and every action to the unauth role, I still get "no current user" when attempting to make the API call without logging in.
Use case is for public author pages, where information about an author along with their currently available books is listed. Users should not have to sign in to see this page, an author should be able to drop a link to the page to anyone, whether they have a login for the app or not.
This is my graphql schema for the relevant types, it gets no errors:
type PublicBook #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byPublicWorld", fields: ["publicWorldId", "indexOrder"])
#key(name:"byPublicSeries", fields: ["publicSeriesId", "indexOrder"]){
id: ID!
publicWorldId: ID
publicSeriesId: ID
indexOrder: Int!
cover: FileObject #connection
description: String
amazon: String
ibooks: String
smashwords: String
kobo: String
goodreads: String
audible: String
barnesnoble: String
sample: String
}
type PublicSeries #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byPublicWorld", fields: ["publicWorldId", "indexOrder"]){
id: ID!
publicWorldId: ID!
indexOrder: Int!
logo: FileObject #connection
description: String
genre: String
books: [PublicBook]#connection(keyName:"byPublicSeries", fields: ["id"])
}
type PublicWorld #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byAuthorPage", fields: ["authorPageId", "indexOrder"]){
id: ID!
authorPageId: ID!
logo: FileObject #connection
description: String
genre: String
indexOrder: Int!
series: [PublicSeries]#connection(keyName:"byPublicWorld", fields: ["id"])
books: [PublicBook]#connection(keyName:"byPublicWorld", fields: ["id"])
}
type AuthorPage #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byPenName", fields: ["penId"])
#key(name:"byPenDisplayName", fields: ["penDisplayName"], queryField: "authorPageByPen"){
id: ID!
authorName: String
penDisplayName: String
penId: ID!
bio: String
photo: FileObject #connection
logo: FileObject #connection
penFBProfile: String
penFBGroup: String
penFBPage: String
penTwitter: String
penInstagram: String
penAmazon: String
penWebsite: String
penNewsletter: String
penGoodreads: String
penPatreon: String
posts: [AuthorPost]#connection(keyName:"byAuthorPage", fields: ["id"])
worlds: [PublicWorld]#connection(keyName:"byAuthorPage", fields: ["id"])
}
type AuthorPost #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byAuthorPage", fields: ["authorPageId", "timeCreated"]){
id: ID!
authorPageId: ID!
timeCreated: AWSTimestamp!
content: String!
title: String!
subtitle: String
type: PostType!
}
Each of these types is set to owner/cognito permissions for creating, updating, and deleting, and then there is a public auth using iam to read. Seems straight forward enough.
The main type here is Author page, and I have the query set up to pull all the connected relevant cascading information. When logged in, this works fine and shows an author page with all the bits and bobs:
export const authorPageByPen = /* GraphQL */ `
query AuthorPageByPen(
$penDisplayName: String
$sortDirection: ModelSortDirection
$filter: ModelAuthorPageFilterInput
$limit: Int
$nextToken: String
) {
authorPageByPen(
penDisplayName: $penDisplayName
sortDirection: $sortDirection
filter: $filter
limit: $limit
nextToken: $nextToken
) {
items {
id
authorName
penDisplayName
penId
bio
photo {
location
}
logo {
location
}
penFBProfile
penFBGroup
penFBPage
penTwitter
penInstagram
penAmazon
penWebsite
penNewsletter
penGoodreads
penPatreon
posts {
nextToken
startedAt
}
worlds {
nextToken
startedAt
}
_version
_deleted
_lastChangedAt
createdAt
updatedAt
owner
}
nextToken
startedAt
}
}
`;
On the page itself (although in production this just happens at app.js and persists throughout the app), I'm pulling current credentials and logging them to make sure that some kind of IAM identity is being created, and it appears to be:
accessKeyId: "BUNCHANUMBERSKEY"
authenticated: false
expiration: Thu Mar 04 2021 13:18:04 GMT-0700 (Mountain Standard Time) {}
identityId: "us-west-2:48cd766c-4854-4cc6-811a-f82127670041"
secretAccessKey: "SecretKeyBunchanumbers"
sessionToken:"xxxxxbunchanumbers"
That identityId on line 4 is present in my identity pool as an unauth identity, so it is getting back to the pool, which seems to be what's supposed to happen.
So, this identity pool has two roles associated with it, which is standard: auth and unauth, and my Unauthenticated Identities Setting has the box for Enable Access to Unauthenticated Identities checked.
In my unauth role, I've got the following as the inline policy json:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"appsync:GraphQL"
],
"Resource": [
"arn:aws:appsync:us-west-2:MyAccountID:apis/MyAppsyncApiId/types/Mutation/fields/authorPageByPen"
]
}
]
}
I wasn't sure if this needed to be mutation, or query, or what, so I've tried them all. I tried them in combination with 'fields' and with 'index', I've tried writing the JSON, and adding the policies from the inline editor, which gives me the following which also does not work:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "appsync:GraphQL",
"Resource": [
"arn:aws:appsync:us-west-2:MyAccountID:apis/MyAppSyncAPIId/types/AuthorPage/fields/authorPageByPen",
"arn:aws:appsync:us-west-2:MyAccountID:apis/MyAppSyncAPIID"
]
}
]
}
What bit am I missing here? I could understand getting some error about not being allowed to access a resource, but the only error that logs is No Current User, and that happens immediately after the log showing the user.
Update:
Running the query from the Appsync console works fine with IAM and no logged in user. In the page itself, I'm using the following function to call the author page (I'm using routes):
const pullAuthorPage = async () => {
try{
const authorPageData = await API.graphql(graphqlOperation(authorPageByPen, { penDisplayName: props.match.params.id.toLowerCase() }))
console.log(JSON.stringify(authorPageData, null, 2));
setState({...authorPageData.data.authorPageByPen.items[0]})
} catch (error) {
console.log(error);
}
}
What I thought would happen with this is that if there is no authenticated user logged in, this will run using the unauth user credentials. Is that not the case? And if so, how should I change it?
I have 2 accounts, one I will call RootAccount where I created an organization and a child account called ChildAccount.
I created a user in the RootAccount called RootUser and a policy in the RootAccount to grant the assume right to the RootUser. I want to create more accounts automatically in the future that's why I limit it to all roles within my children. This is the policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:PrincipalOrgID": "o-xxxxxxxxxx"
}
}
}
]
}
then I created a role in the ChildAccount called ChildRole with this trust policy:
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::1111111111:user/RootUser"
},
"Action": "sts:AssumeRole"
}
]
}
where 1111111111 is the account number of the RootAccount.
Then I created a React app with some sample code where I want assume the role from my RootUser:
const AWS = require("aws-sdk");
AWS.config.update({ accessKeyId: 'RootUserAccessKey', secretAccessKey: 'RootUserSecret', region: "eu-central-1" });
const sts = new AWS.STS()
const checkRights = async () => {
const params = {
RoleArn: 'Arn of ChildRole',
RoleSessionName: 'testsession',
DurationSeconds: 900
}
const session = sts.assumeRole(params,function(err:any,data:any){
if(err)
{
console.log('err:',err)
}
console.log('data:',data)
}).promise()
}
and whenever I run checkRights() I receive an error message 403 Access denied:
AccessDenied: User: {Arn of RootUser} is not authorized to perform: sts:AssumeRole on resource: {Arn of ChildRole}
Any idea what I might be missing? Found an Error in my code/policies? I doublechecked the OrgID, ARNs and assigned the proper policies but it didn't help.
Thanks in advance!
Regards Christian
EDIT:
I removed the PrincipalOrgID Condition but it didn't help, I still receive the same error message. I removed the role, assigned the Assume All Rule and waited ~25 minutes. I also restarted the node server.
What else could be wrong? Or does it take longer until the permissions are updated?
The AWS docs say that this condition key is for resource-based policies. However, you are trying to use in Identity-based policies:
aws:PrincipalOrgID – Simplifies specifying the Principal element in a resource-based policy.
This could explain why your policies don't work the way you expect them to work.
After a lot of troubleshooting and acquiring the aws dev support plan I renewed my credentials for the user and this solved it
I'm having trouble creating a user in an Azure Active Directory with a custom identity. The body of my request looks like this:
{
"passwordProfile": {
"password": "password-value"
},
"accountEnabled": true,
"displayName": "FIRSTNAME LASTNAME",
"passwordPolicies": "DisablePasswordExpiration",
"creationType": "LocalAccount",
"identities": [
{
"issuerAssignedId": "avalid#email.com",
"signInType": "emailAddress",
"issuer": "my_tenant.onmicrosoft.com"
}
]
}
I've also tried a version of the request where I specify mailNickname and userPrincipalName. In every case, the creation fails with the error:
{
"error": {
"innerError": {
"date": "2020-02-20T17:23:48",
"request-id": "c5a7c8da-35bd-4ae2-9ae8-6714b672f035"
},
"message": "One or more properties contains invalid values.",
"code": "Request_BadRequest"
}
}
There's a code snippet in the C# docs that suggests this should be possible.
What am I missing?
Microsoft Graph allows you to manage user accounts in your Azure AD B2C directory by providing create, read, update, and delete methods in the Microsoft Graph API. You can migrate an existing user store to an Azure AD B2C tenant and perform other user account management operations by calling the Microsoft Graph API.
If you try to use this Azure AD Graph API request for a normal Azure AD tenant, it will get the same error massage as yours.
So, ensure the tenant you're trying to query is a B2C tenant.
Try to use the global admin of the B2C tenant (e.g. username#b2ctenant.onmicrosoft.com) to obtain a token. Then use the token in the head to use the API :
Request:
POST https://graph.windows.net/myorganization/users?api-version=1.6
Body Content-Type: application/json:
{
"passwordProfile": {
"password": "password-value"
},
"accountEnabled": true,
"displayName": "FIRSTNAME LASTNAME",
"mailNickname": "mspcai",
"passwordPolicies": "DisablePasswordExpiration",
"creationType": "LocalAccount",
"identities": [
{
"issuerAssignedId": "avalid#email.com",
"signInType": "emailAddress",
"issuer": "my_tenant.onmicrosoft.com"
}
]
}
Looks like you are referring to a beta snippet, please try the following endpoint:
https://graph.microsoft.com/beta/users
I'm using office-js-helpers in order to get an OAuth token in my Outlook web add-in so I can use it for OAuthCredentials with the EWS Managed API (code for that is in an Azure App Service using the ASP.NET Web API).
I have configured my app's application registration in my test Office 365 tenant (e.g. mytenant.onmicrosoft.com, which is NOT the same Azure subscription hosting the web app - if that matters) as a Native app with oauth2AllowImplicitFlow set to true. I used a Native app type instead of a Web/API app to bypass an unexpected error indicating my app requires admin consent - even though no application permissions were requested - but that's another story (perhaps I must use Native anyway - not 100% sure).
I made sure that the Redirect URI (aka reply URL) in the app registration points to the same page as the Outlook add-in (e.g. https://mywebapp.azurewebsites.net/MessageRead.html).
Here is my app manifest:
{
"appId": "a11aaa11-1a5c-484a-b1d6-86c298e8f250",
"appRoles": [],
"availableToOtherTenants": true,
"displayName": "My App",
"errorUrl": null,
"groupMembershipClaims": null,
"optionalClaims": null,
"acceptMappedClaims": null,
"homepage": "https://myapp.azurewebsites.net/MessageRead.html",
"identifierUris": [],
"keyCredentials": [],
"knownClientApplications": [],
"logoutUrl": null,
"oauth2AllowImplicitFlow": true,
"oauth2AllowUrlPathMatching": false,
"oauth2Permissions": [],
"oauth2RequiredPostResponse": false,
"objectId": "a11aaa11-99a1-4044-a950-937b484deb8e",
"passwordCredentials": [],
"publicClient": true,
"supportsConvergence": null,
"replyUrls": [
"https://myapp.azurewebsites.net/MessageRead.html"
],
"requiredResourceAccess": [
{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
},
{
"id": "a42657d6-7f20-40e3-b6f0-cee03008a62a",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
"resourceAccess": [
{
"id": "2e83d72d-8895-4b66-9eea-abb43449ab8b",
"type": "Scope"
},
{
"id": "ab4f2b77-0b06-4fc1-a9de-02113fc2ab7c",
"type": "Scope"
},
{
"id": "5eb43c10-865a-4259-960a-83946678f8dd",
"type": "Scope"
},
{
"id": "3b5f3d61-589b-4a3c-a359-5dd4b5ee5bd5",
"type": "Scope"
}
]
}
],
"samlMetadataUrl": null
}
I also made sure to add the authority URLs to my add-in's manifest:
<AppDomains>
<AppDomain>https://login.windows.net</AppDomain>
<AppDomain>https://login.microsoftonline.com</AppDomain>
</AppDomains>
This is the code I'm using in the add-in for the authentication with office-js-helpers:
// The Office initialize function must be run each time a new page is loaded.
Office.initialize = function(reason) {
$(document).ready(function () {
// Determine if we are running inside of an authentication dialog
// If so then just terminate the running function
if (OfficeHelpers.Authenticator.isAuthDialog()) {
// Adding code here isn't guaranteed to run as we need to close the dialog
// Currently we have no realistic way of determining when the dialog is completely
// closed.
return;
}
// Create a new instance of Authenticator
var authenticator = new OfficeHelpers.Authenticator();
authenticator.endpoints.registerAzureADAuth('a11aaa11-1a5c-484a-b1d6-86c298e8f250', 'mytenant.onmicrosoft.com');
// Add event handler to the button
$('#login').click(function () {
$('#token', window.parent.document).text('Authenticating...');
authenticator.authenticate('AzureAD', true)
.then(function (token) {
// Consume and store the acess token
$('#token', window.parent.document).text(prettify(token));
authToken = token.access_token;
})
.catch(function (error) {
// Handle the error
$('#token', window.parent.document).text(prettify(error));
});
});
});
};
Now the code in the add-in can properly sign in the user and ask for the required permissions, but after clicking the Accept button on the application authorization step the following error is returned:
AADSTS50011: The reply address 'https://mywebapp.azurewebsites.net' does not match the reply addresses configured for the application: 'a11aaa11-1a5c-484a-b1d6-86c298e8f250'. More details: not specified
The error now returns every time I click the Login button (the user is no longer prompted to sign in). It never did retrieve the token. The full auth URL is:
https://login.windows.net/mydomain.onmicrosoft.com/oauth2/authorize?response_type=token&client_id=a11aaa11-484a-b1d6-86c298e8f250&redirect_uri=https%3A%2F%2Fmywebapp.azurewebsites.net&resource=https%3A%2F%2Fgraph.microsoft.com&state=982599964&nonce=3994725115
What am I doing wrong? Could the issue actually be because the host name of the web app (the redirect URI) does not match the domain of the Azure AD tenant hosting the app registration? If so, how can I grant permissions to Exchange Online from my Azure subscription hosting the web app which does not have Office 365 or Exchange Online? Would I have to add an Azure subscription to my test Office 365 tenant so that it can also host a web application??
From your app manifest, I found that you used https://myapp.azurewebsites.net/MessageRead.html as one of the replyUrls.
And below is the url that you are using to get consent from user.
https://login.windows.net/mydomain.onmicrosoft.com/oauth2/authorize?response_type=token&client_id=a11aaa11-484a-b1d6-86c298e8f250&redirect_uri=https%3A%2F%2Fmywebapp.azurewebsites.net&resource=https%3A%2F%2Fgraph.microsoft.com&state=982599964&nonce=3994725115.
If you observe above url, you mentioned redirect_uri as https://myapp.azurewebsites.net. But redirect_uri should match with at least one of the replyUrls you mentioned in the app manifest.
Try to replace https://myapp.azurewebsites.net with https://myapp.azurewebsites.net/MessageRead.html in authorization url.
I have updated them in below url, if you want you can directly try below url.
https://login.windows.net/mydomain.onmicrosoft.com/oauth2/authorize?response_type=token&client_id=a11aaa11-484a-b1d6-86c298e8f250&redirect_uri=https%3A%2F%2Fmywebapp.azurewebsites.net%2FMessageRead.html&resource=https%3A%2F%2Fgraph.microsoft.com&state=982599964&nonce=3994725115
Scenario is Angularjs 1.6.5 app with a c# WebApi. Authentication is done against AAD with the use of angular-adal.js. Up to now, everything Works perfectly, as users are able to login through AAD and WebApi accepts the token.
For this specific app, the roles are in an External application, to which the WebApi has Access. I have been able to add the role claims (after fetching them from the External app) with the use of WindowsAzureActiveDirectoryBearerAuthenticationOptions with the following code inside the ConfigureOAuth(IAppBuilder app):
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudience = clientId
},
//Audience = ConfigurationManager.AppSettings["ida:ClientID"],
Tenant = tenant,
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = async context =>
{
// Retrieve user JWT token from request.
var authorizationHeader = context.Request.Headers["Authorization"];
var userJwtToken = authorizationHeader.Substring("Bearer ".Length).Trim();
// Get current user identity from authentication ticket.
var authenticationTicket = context.Ticket;
var identity = authenticationTicket.Identity;
if (identity.FindFirst(System.Security.Claims.ClaimTypes.Role) == null)
{
var user = identity.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn").Value;
Cis.bll.Xrm.bllSystemUserRoles bllSystemUserRoles = new Cis.bll.Xrm.bllSystemUserRoles();
var su = bllSystemUserRoles.getByEmail(user);
//var roleClaim = new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, su.stringRoles);
foreach (var item in su.Roles)
{
identity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, item.xrmName));
}
}
}
}
});
So for each httpRequest that Angularjs does to the API, the previous function looks up the roles for the user and adds the role claims. With this implementation, I am able to use an AuthorizeAttribute in the Controller methods, restricting Access to only certain roles like so:
[CustomAuthorize(Constants.Roles.resourcesAdministrator)]
I find this way highly inneficient, because with each httpRequest, the API has to fetch the roles of the user from the database (or whatever persistance way is implemented).
What I want to do is to read the user roles just once, and then be able to use them in the API with every subsequent request. Is there a way to add the claims to the token AFTER we recieve the token for AAD?
BTW, I could just add a Roles property to each model, or something like that, but it is not what I'm looking for.
If you have any other ideas or suggestions, they will be greatly appreciated.
Regards
The token is not able to modified since it is issued. And since the roles is stored in the other application, I don't think it is possible to get the roles without query the database.
In this scenario, we can manage the roles though the Azure AD application roles & role claims. Then it will issue the roles claim in the id_token.
For example, we can modify the manifest of the app like below:
"appRoles": [
{
"allowedMemberTypes": [
"User"
],
"displayName": "Writer",
"id": "d1c2ade8-98f8-45fd-aa4a-6d06b947c66f",
"isEnabled": true,
"description": "Writers Have the ability to create tasks.",
"value": "Writer"
},
{
"allowedMemberTypes": [
"User"
],
"displayName": "Observer",
"id": "fcac0bdb-e45d-4cfc-9733-fbea156da358",
"isEnabled": true,
"description": "Observers only have the ability to view tasks and their statuses.",
"value": "Observer"
},
{
"allowedMemberTypes": [
"User"
],
"displayName": "Approver",
"id": "fc803414-3c61-4ebc-a5e5-cd1675c14bbb",
"isEnabled": true,
"description": "Approvers have the ability to change the status of tasks.",
"value": "Approver"
},
{
"allowedMemberTypes": [
"User"
],
"displayName": "Admin",
"id": "81e10148-16a8-432a-b86d-ef620c3e48ef",
"isEnabled": true,
"description": "Admins can manage roles and perform all task actions.",
"value": "Admin"
}
],
And assign the roles to the user through the application via Azure portal like figure below:
Then we can get the id_token like request below(implicit grant flow), the roles should be in the token. And we can call the web API using this token.
Get:https://login.microsoftonline.com/{tenant}/oauth2/authorize?response_type=id_token&client_id={clientId}&redirect_uri={redirect_uri}&nonce={nonce}
id_token sample: