I have an API and other services configured and secured via AWS. We have applied a RBAC style permission system to allow/deny access to resources using groups in Cognito and federated-identities.
When a user logs into to the system, they get a JWT token listing the cognio:roles they have access to, and can use these to perform different actions by requesting a temporary session token for that resource.
I currently have a Angular website, which is working as expected and once the user has logged in, is able to request the session tokens like this:
buildCognitoCreds(idTokenJwt: string, customRoleArn: string) {
let url = 'cognito-idp.' + CognitoUtil._REGION.toLowerCase() + '.amazonaws.com/' + CognitoUtil._USER_POOL_ID;
let logins: CognitoIdentity.LoginsMap = {};
logins[url] = idTokenJwt;
let params = {
IdentityPoolId: CognitoUtil._IDENTITY_POOL_ID, /* required */
Logins: logins,
CustomRoleArn: customRoleArn
};
let serviceConfigs: awsservice.ServiceConfigurationOptions = {};
let creds = new AWS.CognitoIdentityCredentials(params, serviceConfigs);
console.log('Creds for role: ' + customRoleArn + ':', creds);
this.setCognitoCreds(creds);
return creds;
}
I am now trying to do the same in .net as we have a number of desktop and mobile (Xamarin) app that need to access the AWS resources / api.
I have followed the AWS blog here: https://aws.amazon.com/blogs/developer/cognitoauthentication-extension-library-developer-preview/
and am able to authenticate my users against my cognito user pool.
I am now stuck, as how to get the session tokens for a specific role the user has permissions for - I have tried using the following:
var credentials = _authenticationService.User.GetCognitoAWSCredentials(
"us-east-1:ef964b45-939e-4ef3-91c6-xxxxxxxxxxxx", // Identity pool ID
RegionEndpoint.USEast1 // Region
);
var url = "cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxxxx";
var logins = new Dictionary<string, string>();
logins.Add(url, _authenticationService.AuthenticationResult.IdToken);
GetCredentialsForIdentityRequest request = new GetCredentialsForIdentityRequest();
request.IdentityId = "us-east-1:ef964b45-939e-4ef3-91c6-xxxxxxxxxxxxx";
request.Logins = logins;
request.CustomRoleArn = customRoleArn;
var c = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast1);
var test = c.GetIdentityPoolRolesAsync("us-east-1:ef964b45-939e-4ef3-91c6-xxxxxxxxxxxxxx").Result;
var result = c.GetCredentialsForIdentityAsync(request).Result;
Console.WriteLine(result.Credentials.AccessKeyId);
However every time I do I get a: Amazon.CognitoIdentity.Model.NotAuthorizedException with the message:
The ambiguous role mapping rules for: cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxxxx denied this request.
From tracing through the code, and testing it seems that I somehow need to specify the CustomRoleArn to use when getting the cognito was credentials from the Cognito User, otherwise token based rule in the cognito federated identity will return a deny response. - But I can't work out how to do this in the AWS .net SDK......
Related
I am trying to create a PAT using the new capabilities in the TokensHttpClient. However I keep getting authorisation exception. I am using my Microsoft account which is an organization administrator.
VssCredentials creds = new VssClientCredentials();
creds.Storage = new VssClientCredentialStorage();
// Connect to Azure DevOps Services
VssConnection connection = new VssConnection(_uri, creds);
connection.ConnectAsync().SyncResult();
var t = connection.GetClient<TokenAdminHttpClient>();
//next line works as expected
var tokens = t.ListPersonalAccessTokensAsync(connection.AuthorizedIdentity.SubjectDescriptor).Result;
var tokenAdmin = connection.GetClient<TokensHttpClient>();
PatTokenCreateRequest createRequest = new PatTokenCreateRequest();
createRequest.DisplayName = "Niks_Api_Token";
createRequest.Scope = "vso.work_full";
createRequest.ValidTo = DateTime.Now.AddYears(1);
//this is where authorization exception occurs
var result = tokenAdmin.CreatePatAsync(createRequest).Result;
To manage personal access tokens with APIs, you must authenticate with an Azure AD token. Azure AD tokens are a safer authentication mechanism than using PATs. Given this API’s ability to create and revoke PATs, we want to ensure that such powerful functionality is given to allowed users only.
Please check the Prerequisites here.
Make sure your org has been connect to AAD, see here.
Please register an application in Azure AD, make sure the client secret has been created. You can refer to this doc. And add the permission of Azure DevOps.
The sample code to get Azure AD access token.
public static async Task<string> GetAccessTokenAsyncByClientCredential()
{
IConfidentialClientApplication cca = ConfidentialClientApplicationBuilder.Create(<appId/clientId>)
.WithTenantId(<tenantId>)
.WithClientSecret(<clientSecret>)
.Build();
string[] scopes = new string[] { "499b84ac-1321-427f-aa17-267ca6975798/.default" };
var result = await cca.AcquireTokenForClient(scopes).ExecuteAsync();
return result.AccessToken;
}
I am applying Google Login with CognitoCachingCredentialsProvider and AmazonCognitoIdentityClient but user entry added in the identity pool, not in the user pool. I need to get JWT token for auth. I did research on lots of document but no one gives a proper solution. Please help me with how to achieve social login with AWS Cognito.
AmazonCognitoIdentityClient cognitoClient = new AmazonCognitoIdentityClient(AWSConnection.AWS_AccessKey,AWSConnection.AWS_SecretKey,AWSConnection.AWSRegion);
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>() { };
keyValuePairs.Add("accounts.google.com", idToken);
GetIdRequest idRequest = new GetIdRequest
{
//idRequest.AccountId = _openIdConnect.ClientId;
IdentityPoolId = "us-west-2:xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx",
// set the Dictionary of logins if you are authenticating users
// through an identity provider
Logins = keyValuePairs
};
// The identity id is in the IdentityId parameter of the response object
GetIdResponse idResp = await cognitoClient.GetIdAsync(idRequest);
// TODO: At this point, you should save this identifier so you won’t
// have to make this call the next time a user connects
// create a new request object
GetOpenIdTokenRequest openIdReq = new GetOpenIdTokenRequest
{
IdentityId = idResp.IdentityId,
// set the Dictionary of logins if you are authenticating users
// through an identity provider
Logins = keyValuePairs
};
GetOpenIdTokenResponse openIdResp = await cognitoClient.GetOpenIdTokenAsync(openIdReq);
I am getting IdToken but I need JWT token for the same.
Also, I used this code.
// Initialize the Amazon Cognito credentials provider
CognitoAWSCredentials credentials = new CognitoAWSCredentials(
"us-west-2:xxxxxxx-xxxx-xxx-xxxx-xxxx", // Identity pool ID
RegionEndpoint.USWest2 // Region
);
credentials.AddLogin("accounts.google.com", idToken);
var sd = credentials.GetCredentials();
I am getting idToken token from google that is id_token.
I have created an Azure AAD app of type webapp which has client secret and redirect url. Now, I want to get an access token on behalf of user using the AAD app. From looking at the documentation, I got the following code so far.
static void Main(string[] args)
{
var clientId = "<REDACTED>";
var clientSecret = "<REDACTED>";
var resourceAppIdURI = "https://api.office.com/discovery/";
var authority = "https://login.microsoftonline.com/common";
AuthenticationContext ac = new AuthenticationContext(authority, new FileCache());
ClientCredential cc = new ClientCredential(clientId, clientSecret);
// Get token as application
var task = ac.AcquireTokenAsync(resourceAppIdURI, cc);
task.Wait();
var appToken = task.Result.AccessToken;
// Get tokenn on behalf of user
UserCredential uc = new UserCredential("usrname#mytenant.com");
task = ac.AcquireTokenAsync(resourceAppIdURI, clientId, uc);
var userToken = task.Result.AccessToken;
Console.ReadLine();
}
But this is the error I get when I try to get user token is as follows.
Message "AADSTS70002: The request body must contain the following
parameter: 'client_secret or client_assertion'.\r\nTrace ID:
0e977f67-d5cb-4cf5-8fea-bac04b6d0400\r\nCorrelation ID:
824a96bf-8007-4879-970c-2680644b8669\r\nTimestamp: 2017-07-21
05:02:41Z" string
Why am I getting this error and how to fix it?
Do I need to login with the user first and then use UserAssertion instead ?
There are tonnes of overloaded methods for AcquireTokenAsync method, but not sure what I should use.
I also looked at this github url to see how they are doing it
https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof/blob/8afb3e6a648d8e7246685bf6747d009006c761b8/TodoListService/Controllers/TodoListController.cs
This is the relevant code to get token as logged in user
ClientCredential clientCred = new ClientCredential(clientId, appKey);
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
string userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null ? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value : ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
string userAccessToken = bootstrapContext.Token;
UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(authority, new DbTokenCache(userId));
Here they already have a logged in user and creating a UserAssertion from that loggedin user's token. In my console app, the user hasn't logged in yet.
So I need a way to do this in my console app. How can I show the AAD login page to the user as a pop-up and then once the user enters creds use that info to create an UserAssertion object?
thanks
Your scenario is a native application that calls a web API on behalf of a user . The native application could obtains an access token for the user by using the OAuth 2.0 authorization code grant , then access token is then sent in the request to the web API, which authorizes the user and returns the desired resource :
Please read more about the description of protocol flow here .Also see the code samples for Native Application to Web API scenarios.
In addition , you could click here for code sample about how to call the Azure AD Graph API from a native client(.net console application ) ,it uses the Active Directory Authentication Library (ADAL) for authentication .
I have succesfully setup a multi tenant application.
For now, I am able to authenticate the user and use tokens to access other resources. (Microsoft Graph & Microsoft AD Graph)
Now I want to get B2B working.
Current flow:
- User signs in
- AuthorizationCodeReceived gets the acquires the token (via $commonAuthority endpoint)
- When requesting a token for the Ad Graph, I am using the $tenantAuthority
This works perfectly when $tenantAuthority is the same tenant authority as the one where the account was created in.
However, if I login with another user (from another tenant, given trust to the actual tenant) and use $tenantAuthority = trusted authority, then I always the following error:
Failed the refresh token:
AADSTS65001: The user or administrator has not consented to use the application with ID
If I change $tenantAuthority to the 'source' tenant authority where the user was created in, everything works fine.
Any help would be greatly appreciated.
Update: Code sample
App has two tenants (tenantA en tenantB) and I will use a user from tenantB with tenantA given a trust to this user.
AuthorizationCodeReceived = async context =>
{
TenantContext.TenantId = "someguid";
var tenantId =
TenantContext.TenantId;
// get token cache via func, because the userid is only known at runtime
var getTokenCache = container.Resolve<Func<string, TokenCache>>();
var userId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.ObjectIdentifier).Value;
var tokenCache = getTokenCache(userId);
var authenticationContext = new AuthenticationContext($"{configuration.Authority}",
tokenCache);
await authenticationContext.AcquireTokenByAuthorizationCodeAsync(
context.Code,
new Uri(context.Request.Uri.GetLeftPart(UriPartial.Authority)),
new ClientCredential(configuration.ClientId, configuration.ClientSecret),
configuration.GraphResourceId);
}
This code works perfectly. Login in with a user from both tenants works perfectly.
But when I need the Graph Service Client or ActiveDirectoryClient, I need to obtain access tokens to been able to address an api for a certain tenant. I retrieve the access tokens like this:
public IGraphServiceClient CreateGraphServiceClient()
{
var client = new GraphServiceClient(
new DelegateAuthenticationProvider(
async requestMessage =>
{
Logger.Debug("Retrieving authentication token to use in Microsoft Graph.");
string token;
var currentUserHomeTenantId = TenantContext.TenantId;
var currentUserObjectId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.ObjectIdentifier).Value;
var authenticationContext =
new AuthenticationContext($"{_configuration.TenantAuthorityPrefix}{currentUserHomeTenantId}",
_tokenCacheFactoryMethod(currentUserObjectId));
var clientCredential = new ClientCredential(_configuration.ClientId, _configuration.ClientSecret);
try
{
token = await GetTokenSilently(authenticationContext, _configuration.GraphResourceId, currentUserObjectId);
}
catch (AdalSilentTokenAcquisitionException e)
{
Logger.Error("Failed to retrieve authentication token silently, trying to refresh the token.", e);
var result = await authenticationContext.AcquireTokenAsync(_configuration.GraphResourceId, clientCredential);
token = result.AccessToken;
}
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderKeys.Bearer, token);
}));
return client;
}
public IActiveDirectoryClient CreateAdClient()
{
var currentUserHomeTenantId = TenantContext.TenantId;
var currentUserObjectId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.ObjectIdentifier).Value;
var graphServiceUrl = $"{_configuration.AdGraphResourceId}/{currentUserHomeTenantId}";
var tokenCache = _tokenCacheFactoryMethod(currentUserObjectId);
var client = new ActiveDirectoryClient(new Uri(graphServiceUrl),
() => GetTokenSilently(
new AuthenticationContext(
$"{_configuration.TenantAuthorityPrefix}{ClaimsPrincipal.Current.FindFirst(ClaimTypes.TenantId).Value}", tokenCache
),
_configuration.AdGraphResourceId, currentUserObjectId
));
return client;
}
When I do a request with one of the two client SDK's, I got the following error:
Failed the refresh token: AADSTS65001: The user or administrator has not consented to use the application with ID.
Changing the catch method when retrieving the Token did the trick:
if(e.ErrorCode == "failed_to_acquire_token_silently")
{
HttpContext.Current.Response.Redirect(authenticationContext.GetAuthorizationRequestUrlAsync(resourceId, _configuration.ClientId, new Uri(currentUrl),
new UserIdentifier(currentUserId, UserIdentifierType.UniqueId), string.Empty);
}
I don't see that you mention that so: in a B2B collaboration you've to invite user from other tenant first. The steps are like that:
invite and authorize a set of external users by uploading a comma-separated values - CSV file
Invitation will be send to external users.
The invited user will either sign in to an existing work account with Microsoft (managed in Azure AD), or get a new work account in Azure AD.
After signed in, user will be redirected to the app that was shared with them
That works perfectly in my case.
Regarding some problems which I've detect:
Trailing "/" at the end of the active directory resource - try to remove it as this may cause problems. Bellow you will find some code to get authentication headers:
string aadTenant = WebServiceClientConfiguration.Settings.ActiveDirectoryTenant;
string clientAppId = WebServiceClientConfiguration.Settings.ClientAppId;
string clientKey = WebServiceClientConfiguration.Settings.ClientKey;
string aadResource = WebServiceClientConfiguration.Settings.ActiveDirectoryResource;
AuthenticationContext authenticationContext = new AuthenticationContext(aadTenant);
ClientCredential clientCredential = new ClientCredential(clientAppId, clientKey);
UserPasswordCredential upc = new UserPasswordCredential(WebServiceClientConfiguration.Settings.UserName, WebServiceClientConfiguration.Settings.Password);
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(aadResource, clientAppId, upc);
return authenticationResult.CreateAuthorizationHeader();
Applications provisioned in Azure AD are not enabled to use the OAuth2 implicit grant by default. You need to explicitly opt in - more details can be found here: Azure AD OAuth2 implicit grant
I am attempting to download metric data from Google Analytics using C# and am performing user authentication with OAuth 2.0. I'm using the Installed Application authorisation flow, which requires logging into Google and copy-and-pasting a code into the application. I'm following the code taken from google-api-dotnet-client:
private void DownloadData()
{
Service = new AnalyticsService(new BaseClientService.Initializer() {
Authenticator = CreateAuthenticator(),
});
var request = service.Data.Ga.Get(AccountID, StartDate, EndDate, Metrics);
request.Dimensions = Dimensions;
request.StartIndex = 1;
request.MaxResults = 10000;
var response = request.Execute(); // throws Google.GoogleApiException
}
private IAuthenticator CreateAuthenticator()
{
var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description) {
ClientIdentifier = "123456789012.apps.googleusercontent.com",
ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxx",
};
return new OAuth2Authenticator<NativeApplicationClient>(provider, Login);
}
private static IAuthorizationState Login(NativeApplicationClient arg)
{
// Generate the authorization URL.
IAuthorizationState state = new AuthorizationState(new[] { AnalyticsService.Scopes.AnalyticsReadonly.GetStringValue() });
state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);
Uri authUri = arg.RequestUserAuthorization(state);
// Request authorization from the user by opening a browser window.
Process.Start(authUri.ToString());
Console.Write("Google Authorization Code: ");
string authCode = Console.ReadLine();
// Retrieve the access token by using the authorization code.
state = arg.ProcessUserAuthorization(authCode, state);
return state;
}
The Google account xxxxxx#gmail.com registered the Client ID and secret. The same account has full administration rights in Google Analytics. When I try to pull data from Google Analytics, it goes through the authorisation process, which appears to work properly. Then it fails with:
Google.GoogleApiException
Google.Apis.Requests.RequestError
User does not have sufficient permissions for this profile. [403]
Errors [
Message[User does not have sufficient permissions for this profile.] Location[ - ] Reason [insufficientPermissions] Domain[global]
]
I've been struggling with this for a few hours. I've double checked that the correct user is being used, and is authorised on Google Analytics. I'm at a loss as to what is misconfigured. Any ideas as to what requires configuring or changing?
If auth seems to be working working then my suggestion is that you make sure you're providing the correct ID because based on your code snippet:
var request = service.Data.Ga.Get(AccountID, StartDate, EndDate, Metrics);
one can only assume that you're using the Account ID. If so, that is incorrect and you'd receive the error you've encountered. You need to query with the Profile ID.
If you login to Google Analytics using the web interface you'll see the following pattern in URL of the browser's address bar:
/a12345w654321p9876543/
The number following the p is the profile ID, so 9876543 in the example above. Make sure you're using that and actually you should be using the table id which would be ga:9876543.
If it isn't an ID issue then instead query the Management API to list accounts and see what you have access to and to verify auth is working correctly.
This can help : https://developers.google.com/analytics/devguides/reporting/core/v3/coreErrors, look error 403.
//Thanks for this post. The required profile id can be read from the account summaries.
Dictionary profiles = new Dictionary();
var accounts = service.Management.AccountSummaries.List().Execute();
foreach (var account in accounts.Items)
{
var profileId = account.WebProperties[0].Profiles[0].Id;
profiles.Add("ga:" + profileId, account.Name);
}