Authenticating to Azure Devops Git repo - c#

We are working on a system to automatically commit generated projects into Git repositories located in Azure Devops. We want to use libgit2sharp for this. We want the user to authenticate using their Microsoft account, grab the Access Token(jwt) from the authentication request and use that as means of authentication. But we cannot seem to get this working.
In another post I read 2 other authentication methods: 1. Alternative accounts. 2. Personal Access Tokens, PAT. Both made in the profile sections of your devops account.
I can get Alternative accounts to work perfectly but this is not our preferred route as it will require extra actions from the user. The PAT does not seem to work for me and throws me an error that there were "too many redirects or authentication replays". I figured this is because of the two factor authentication that is enabled on the Microsoft account.
Is it even supported to use an Access Token(jwt) in LibGit2Sharp with 2FA enabled?
using (var repo = new Repository({repo location}))
{
foreach (var file in file)
{
repo.Index.Add(file.Path);
repo.Index.Write();
}
var author = new Signature("{name}", "{name}", DateTime.Now);
var committer = author;
repo.Commit("Here's a commit i made!", author, committer);
var options = new PushOptions();
options.CredentialsProvider = (url, user, cred) => new UsernamePasswordCredentials() { Username = "{username}", Password = "{password}" };
repo.Network.Push(repo.Branches["master"], options);
}

Personal Access Tokens (PAT) do bypass MFA so that is probably not the error you're getting. A PAT is your best option, at the moment of the push you need the remote-url of your local Git repo to be as follows:
https://pat:{PAT_HERE}#dev.azure.com/...
e.g.
https://pat:gaakbfootuial7ksj4uv55o52335tyhhaasbqdvbg5xgyy33t754#dev.azure.com/auroraloop/devenv/_git/devenv
Tips:
The PAT will take the permissions of the Azure DevOps user that generated it so the user must have contribute permissions to the repo.
You cannot automate PAT creation, you must have it upfront stored somewhere, if you don't want to hardcode this (you shouldn't) consider using an Azure Key Vault to store and retrieve the value I'm sure they have c# libraries.
The options.CredentialsProvider part of your code is probably doing exactly what I mentioned about the remote-url try setting it as follows:
options.CredentialsProvider = (url, user, cred) => new UsernamePasswordCredentials() { Username = "pat", Password = "{PAT_HERE}" };
Hardcode the PAT for testing and if successful take a look at the Azure Key Vault approach.

Related

Not able to get all users from Azure Active Directory

I am using solution mentioned here to get all users from Active Directory however I suspect the code is pulling disabled users from our old Active Directory. The new one is Azure Active Directory. Please let me know what change is required to get below details of only active users from Azure Active Directory:
First Name
Last Name
Email
Enterprise ID
Getting all users in Azure AD can use Microsoft Graph API. Here's the API for listing users. But it doesn't support personal Microsoft account, it only supports work or school accounts. By the way, I'm not sure what is Enterprise ID, could you pls take a look at this section to check if this API contained it?
I assume you have an asp.net core WEB API which is used to getting user list. So you should use code like below.
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "aad_app_id";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var users = await graphClient.Users.Request().GetAsync();
Then an Azure AD application is required for the variables in code above. Pls follow this document to register the Azure AD app. Since my assumption is based on a web API, no need to add redirect URL here. Now we can get tenantId , clientId in Overview blade, and created the client secret. We also need to modify API permissions blade and add required API permissions. What we need is Application permission User.Read.All,User.ReadWrite.All,Directory.Read.All, Directory.ReadWrite.All.

ADAL query security groups

I have a system that logs into a native wpf app via an web api on azure.
AuthenticationContext authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", authority));
AuthenticationResult tokenAuthResult = authContext.AcquireTokenAsync(resource, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto)).Result;
if (tokenAuthResult == null) return;
Credentials.RestCredentials = new TokenCredentials(tokenAuthResult.AccessToken);
Credentials.UserName = string.Concat(tokenAuthResult.UserInfo.GivenName, " ", tokenAuthResult.UserInfo.FamilyName);
This all works perfect, with returning token etc.
The users are all in ADAL, and have associated groups against them (these are all O365 users).
I want to be able to query what the logged in users associated group(s) are.
Do I need to make a new call out using the Graph api?
Do I use the returned token?
I'm a little lost here.
Thanks in advance
Scott
to add to #juunas's answer, the following sample active-directory-dotnet-webapp-groupclaims explains things in details.
In particular Step 3: Configure your application to receive group claims explains the app configuration in the manifest.
See also the claims related to groups in https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-token-and-claims#claims-in-idtokens
An easy way is to go to your app registration in Azure AD, click on Manifest, and modify the groupMembershipClaims property:
"groupMembershipClaims": "SecurityGroup",
This will result in the Id token containing the user's group ids.
It does have a limit though, only a certain amount of groups can be included, in which case you would have to get them from Graph API instead.
Here is the endpoint you would need to call in that case: https://msdn.microsoft.com/en-us/library/azure/ad/graph/api/functions-and-actions#getMemberGroups.

Create Microsoft Graph GraphServiceClient with user/password unattended

I am creating a console application that connects to Microsoft Graph using the Microsoft Graph API (as shown in https://github.com/microsoftgraph/console-csharp-connect-sample).
Everything is working fine, but I wonder if there is a way where I can authenticate a user (when I already know their user/password) without them needing to manually enter their credentials on the "Sing in to your account" window rendered on the desktop.
The idea is basically to run the application unattended, so there is no need for the user to be entering their credentials when the application starts. I canĀ“t find any relevant information on the subject.
Is that even possible?
EDIT
After following the link #DanSilver posted about geting access without a user, I tried the sample suggested in that link (https://github.com/Azure-Samples/active-directory-dotnet-daemon-v2). Although that is an MVC application that forces users to authenticate (precisely what I wanted to avoid) I have managed to use part of the authentication code in that sample with my console application. After giving authorization to the application manually through a request to https://login.microsoftonline.com/myTenantId/adminconsent I can create a GraphServiceClient in my console app that connects to Graph without user interaction. So I mark the answer as valid.
Just in case someone is in the same situation, the GraphServiceclient is created as:
GraphServiceClient graphServiceClientApplication = new GraphServiceClient("https://graph.microsoft.com/v1.0", new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string clientId = "yourClientApplicationId";
string authorityFormat = "https://login.microsoftonline.com/{0}/v2.0";
string tenantId = "yourTenantId";
string msGraphScope = "https://graph.microsoft.com/.default";
string redirectUri = "msalXXXXXX://auth"; // Custom Redirect URI asigned in the Application Registration Portal in the native Application Platform
string clientSecret = "passwordGenerated";
ConfidentialClientApplication daemonClient = new ConfidentialClientApplication(clientId, String.Format(authorityFormat, tenantId), redirectUri, new ClientCredential(clientSecret), null, new TokenCache());
AuthenticationResult authResult = await daemonClient.AcquireTokenForClientAsync(new string[] { msGraphScope });
string token = authResult.AccessToken;
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
));
One idea is using the "app only" authorization flow. The idea is that you can have long running apps access the Microsoft Graph without user authentication. The main difference is instead of the access token granting access to a particular user, it grants your app access to resources that you've consented to in advance. There will be no user login dialog and you can programmatically fetch access tokens to call the Graph API.
To reiterate that these tokens aren't for a particular user, consider making a GET request to 'https://graph.microsoft.com/v1.0/me'. This will return an error since the access token isn't for a particular user and "me" doesn't mean anything. Requests should be sent with full user ids "like graph.microsoft.com/users/someuser#contosos.com".
More information on this can be found at the Get access without a user documentation page.
Another idea is to let the user authenticate the first time they use your app and then store a refresh token. These tokens live longer (a few months IIRC) and then you won't need to prompt for user consent each time the app runs. Refresh tokens can be exchanged for access tokens that live 60 minutes and those can be used to call Graph API on behalf of users.
More info on refresh tokens: https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_user#5-use-the-refresh-token-to-get-a-new-access-token
I did want to come back out here and share, since I ran into this problem yesterday, and the idea of granting read/write mailbox access for my application... to EVERYONE'S EMAIL BOX IN THE ENTIRE ORGANIZATION... was way over the top for my needs. (And that is exactly what happens when you start talking about granting Application level permissions instead of delegated permissions to your registered app).
It's a simple use case: I had a nightly process that needed to automate sending of emails from a shared mailbox using a traditional AD service account.
Thankfully... even though they are on the march to eliminate passwords (lol)... someone at Microsoft still recognizes my use case, and it's lack of apples-to-apples alternatives in Azure AD. There is still an extension method we can lean on to get the job done:
private AuthenticationContext authContext = null;
authContext = new AuthenticationContext("https://login.microsoftonline.com/ourmail.onmicrosoft.com",
new TokenCache());
result = authContext.AcquireTokenAsync("https://graph.microsoft.com/",
"12345678-1234-1234-1234-1234567890",
new UserPasswordCredential(
Environment.GetEnvironmentVariable("UID", EnvironmentVariableTarget.User),
Environment.GetEnvironmentVariable("UPD", EnvironmentVariableTarget.User)
)).Result;
You can replace those GetEnvironmentVariable calls with your Username (UID) and Password (UPD). I just stuff them in the environment variables of the service account so I didn't have to check anything into source control.
AcquireTokenAsync is an extension method made available from the Microsoft.IdentityModel.Clients.ActiveDirectory namespace. From there, it's a simple business to fire up a GraphClient.
string sToken = result.AccessToken;
Microsoft.Graph.GraphServiceClient oGraphClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) => {
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", sToken);
return Task.FromResult(0);
}));
The last bit of magic was to add these permissions to Application registration I created in Azure AD (where that GUID came from). The application has be defined as a Public client (there's a radio button for that towards the bottom of the authentication tab). I added the following 5 DELEGATED permissions (NOT application permissions):
Microsoft Graph
1. Mail.ReadWrite.Shared
2. Mail.Send.Shared
3. User.Read
4. email
5. openid
Since user consents are actually blocked in our organization, another permissions admin had to review my application definition and then do an admin level grant of those rights, but once he did, everything lit up and worked like I needed: limited access by a service account to a single shared mailbox, with the actual security of that access being managed in Office 365 and not Azure AD.

Azure AD authentication failing when using ms live accounts other than my AAD acount

I trying to read user data from their Microsoft live account. I have written code as below:
public void GetUserData(){
var authContext = new AuthenticationContext("https://login.microsoftonline.com/common/");
var result = _authenticationContext
.AcquireTokenAsync("https://graph.microsoft.com", "<my client/app ID>", "<redirect URI>", new PlatformParameters(PromptBehavior.RefreshSession))
.Result;
var accessToken = result.AccessToken;
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",accessToken);
var userResponse = httpClient.GetStringAsync("https://graph.microsoft.com/beta/me/").Result;
//DO SOMTHING WITH DATA
}
my code is working fine when I used my AAD credentials, but when I used my personal account it is giving the following error.
AADSTS50020: User account 'XXXX#outlook.com' from identity provider
'live.com' does not exist in tenant 'Default Directory' and cannot
access the application 'XXXXXXXXXXXXXXXXX' in that
tenant. The account needs to be added as an external user in the
tenant first. Sign out and sign in again with a different Azure Active
Directory user account.
Here is the screenshot:
It's similar to this question. could someone help me out?
v1 endpoints require that the user is a member in a directory.
You should probably use the v2.0 endpoints for this: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-appmodel-v2-overview
If you expect only consumer MS accounts to login, you can specify the authorize URL as:
https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize
First, for the error massage in your question, you need to add the live account into your directory first and then try to use Azure AD v2 endpoint to authenticate. You can not sign in the app with the external account which was not in that directory.
I assume that you want any Microsoft live account can use your app.
Based on this requirement, I suggest you can use Azure AD B2C to achieve this. Azure AD B2C can enables your application to authenticate with any Microsoft account. You can add Microsoft Account as a social identity providers. So that any live accounts can sign up and sign in your App through Azure AD B2C.
You can see more details about Providing sign-up and sign-in to consumers with Microsoft accounts in this official document.
Hope this helps.

Is it possible to get the canonical user id from AWS IAM users, from the .NET API?

I have successfully created a user, credentials, and a bucket.
Now I need to grant bucket access to this user.
Is there any way to get this CanonicalUser value from code?
The IAM user object only provides ARN, Path, UserId and UserName values, but none of these are valid for the grant.
using (var s3 = new Amazon.S3.AmazonS3Client("[user_key]", "[secret_user_key]", RegionEndpoint.GetBySystemName("eu-west-1")))
{
var response = s3.GetACL("[bucket_id]");
var acl = response.AccessControlList;
acl.AddGrant(
new S3Grantee() {
CanonicalUser = **???**
},
new S3Permission(S3Permission.FULL_CONTROL)
);
s3.PutACL(
new PutACLRequest() {
AccessControlList = acl,
BucketName = "[bucket_id]"
}
);
}
You can easily get CanonicalUser ID using ListAllMyBuckets API call [1] (s3:ListAllMyBuckets permission is required):
$ aws s3api list-buckets --query Owner
{
"DisplayName": "lord-vader",
"ID": "f420064cb076f772e10584fc40ab777c09f6b7d154342cf358f1bd1e573c9cf7"
}
In AWS SDK for .NET, use code like this [2]:
AmazonS3Client client = new AmazonS3Client();
ListBucketsResponse response = client.ListBuckets();
Console.WriteLine("Canonical user ID - {0}", response.Owner.Id);
In AWSJavaSDK you can use AmazonS3.getS3AccountOwner wrapper method [3].
https://docs.aws.amazon.com/AmazonS3/latest/API/RESTServiceGET.html
https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/S3/MS3ListBuckets.html
https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3Client.html#getS3AccountOwner--
No, it is not possible to get the canonical user id from code - you've hit a somewhat odd and likely legacy aspect due to the different way to manage access permissions for S3 resources, see the AWS team's response to How to find out Canonical ID for an IAM user?:
You can not add IAM Users to ACL's as a grantee. I'll have the documentation updated to clarify that IAM Users are not supported in ACL's. There are a few solutions you can use to grant this User access to your Amazon S3 content: [...]
You might indeed want to reconsider using the more versatile S3 bucket policies instead (see below) - however, if you have access to the account's root credentials, you might find the canonical user ID associated with your AWS account as outlined in Specifying a Principal in a Policy (mind you, this doesn't work with IAM user credentials):
Go to http://aws.amazon.com and from the My Account/Console drop-down menu, select Security Credentials.
Sign in using appropriate account credentials.
Click Account Identifiers.
I shall emphasize again that AWS strongly recommends to only use IAM users these days, see e.g. Root Account Credentials vs. IAM User Credentials:
Because you can't control the privileges of the root account
credentials, you should store them in a safe place and instead use AWS
Identity and Access Management (IAM) user credentials for day-to-day
interaction with AWS.
This canonical user id requirement for S3 is a rare exception, and as I said likely to be considered a legacy artifact due to S3's ACL layer predating IAM, thus best avoided, if possible.

Categories