Get Azure AD Access token from .Net Standard 2.0 unit test - unit-testing

I am having a .Net Core 2.0 asp.net mvc web application. For the same I have a .Net Standard 2.0 unit test project. For the unit test written I have to call a Azure AD protected Web API. Can anyone let me know how can we get azure ad access token from a unit test project in .net standard 2.0.
It is possible in .Net framework as it has "UserPasswordCredential" class available in the "Microsoft.IdentityModel.Clients.ActiveDirectory" dll. But this class is removed in .Net Standard 2.0 (https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/issues/482)

I've used something like this for integration tests:
string tokenUrl = _authority + "oauth2/token";
var req = new HttpRequestMessage(HttpMethod.Post, tokenUrl)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "password",
["client_id"] = settings.ClientId,
["client_secret"] = settings.ClientSecret,
["resource"] = _resourceUri,
["username"] = settings.UserName,
["password"] = settings.Password
})
};
HttpResponseMessage res = await _client.SendAsync(req);
string json = await res.Content.ReadAsStringAsync();
AadTokenResponse tokenResponse = JsonConvert.DeserializeObject<AadTokenResponse>(json);
There's a few class-level fields like the AAD authority, API resource URI and an HttpClient.
So what this does is acquire an access token using the Resource Owner Password Credentials Grant flow.
This is one of those few cases where using this flow actually makes sense.
We acquire an access token in a user's context without a login window.
This flow should not be used when something better is available, and in this case also requires that the user is not federated, does not have MFA etc.
You'll probably want to cache the token so you don't hammer the token endpoint from your tests pointlessly.

Related

3-legged OAuth and one-time-code flow using google-auth-library-ruby with google-api-ruby-client

Quick Overview: I have a ruby app that runs nightly and does something with a user's google calendar. The user has already given access via a separate react app. I'm having trouble getting the ruby app to access the user's calendar with the authorization code from the react app.
Details: I have a React front-end that can sign in a user using gapi and subsequently sign the user into Firebase. Here is how I configure the gapi obj:
this.auth2 = await loadAuth2WithProps({
apiKey: config.apiKey, // from firebase
clientId: config.clientId, // from gcp
// ....
access_type: "offline", // so we get get authorization code
})
Here is sign in:
doSignInWithGoogle = async () => {
const googleUser = await this.auth2.signIn();
const token = googleUser.getAuthResponse().id_token;
const credential = app.auth.GoogleAuthProvider.credential(token);
return this.auth.signInWithCredential(credential);
};
The user's next step is to grant the app offline access to their calendar:
doConnectGoogleCalendar = async () => {
const params = {scope:scopes};
const result = await this.auth2.grantOfflineAccess(params);
console.log(result.code); // logs: "4/ygFsjdK....."
};
At this point the front end has the authorization code that can be passed to a server-side application to be exchanged for access and refresh tokens. I haven't been able to find a good way to use a user supplied auth-code to make calls to available scopes. This is how I've configured the oauth client:
auth_client = Google::APIClient::ClientSecrets.load(
File.join(Rails.root,'config','client_secrets.json') // downloaded from GCP
).to_authorization
^ I'm using the same GCP Credentials on the backend that I'm using for the frontend. It is a "OAuth 2.0 Client ID" type of credential. I'm unsure if this is good practice or not. Also, do I need to define the same config that I do on the frontend (like access_type and scope)?.
Next I do what the docs say to get the access and refresh tokens(click Ruby):
auth_client.code = authorization_code_from_frontend
auth_client.fetch_access_token!
---------
Signet::AuthorizationError (Authorization failed. Server message:)
{
"error": "invalid_grant",
"error_description": "Bad Request"
}
Is there something I'm missing in setting up a separate backend application that can handle offline access to a user granted scope? There is so much different information on these libraries but I haven't been able to distill it down to something that works.
UPDATE
I found this page describing the "one-time-code flow" which I haven't found anywhere else is all of the docs I've gone through. It does answer one of my minor questions above: Yes, you can use the same client secrets as the web application for the backend. (see the full example at the bottom where they do just that). I'll explore it more and see if my bigger problem can be resolved. Also going to update the title to include one-time-code flow.
After a good amount of digging through code samples and source code, I have a clean working solution. Once I found the page in my "update" it led me to finding out that ClientSecrets way I was doing things had been deprecated in favor of the google-auth-library-ruby project. I'm glad I found it because it seems to be a more complete solution as it handles all of the token management for you. Here is code to setup everything:
def authorizer
client_secrets_path = File.join(Rails.root,'config','client_secrets.json')
client_id = Google::Auth::ClientId.from_file(client_secrets_path)
scope = [Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY]
redis = Redis.new(url: Rails.application.secrets.redis_url)
token_store = Google::Auth::Stores::RedisTokenStore.new(redis: redis)
Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store, "postmessage")
end
and then this is how I use the authorization code:
def exchange_for_token(user_id,auth_code)
credentials_opts = {user_id:user_id,code:auth_code}
credentials = authorizer.get_and_store_credentials_from_code(credentials_opts)
end
after calling that method the library will store the exchanged tokens in Redis (you can configure where to store) for later use like this:
def run_job(user_id)
credentials = authorizer.get_credentials(user_id)
service = Google::Apis::CalendarV3::CalendarService.new
service.authorization = credentials
calendar_list = service.list_calendar_lists.items
# ... do more things ...
end
There is so much info out there that it is difficult to isolate what applies to each condition. Hopefully this helps anyone else that gets stuck with the "one-time-code flow" so they don't spend days banging their head on their desk.

Is there a REST API for TFS that functions without Azure DevOps?

I've been assigned to build a set of tools for DevOps for improving our turnaround time on building several branches. These tools will need to interact with our source control, which is currently running on Team Foundation Server. When I asked if we had access to Azure DevOps, which according to the tutorials I found online was needed to create an authentication token that could be used to access TFS through it's default REST API, I was told that we did not and had no plans to, because this company was an AWS organization.
Does anyone know if there's a way to access a REST or similar API for TFS if all you have access to is TFS running on AWS?
Yeah, as mentioned in the comment, you should use PAT to authenticate
TFS through Rest API. Username should be blank. PAT is the password. There are multiple related tutorials in google, a sample for your refer:
using System.Net.Http;
using System.Net.Http.Headers;
...
//encode your personal access token
string credentials = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", personalAccessToken)));
ListofProjectsResponse.Projects viewModel = null;
//use the httpclient
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://{accountname}.visualstudio.com"); //url of our account
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);
//connect to the REST endpoint
HttpResponseMessage response = client.GetAsync("_apis/projects?stateFilter=All&api-version=1.0").Result;
//check to see if we have a succesfull respond
if (response.IsSuccessStatusCode)
{
//set the viewmodel from the content in the response
viewModel = response.Content.ReadAsAsync<ListofProjectsResponse.Projects>().Result;
//var value = response.Content.ReadAsStringAsync().Result;
}
}
Since you are using PAT with on-premise TFS server, make sure you already turned off Basic Authentication on server. Otherwise you will get an returned error.
We recommend you keep IIS Basic Authentication turned off at all times
when using Azure DevOps Server. Only if necessary should you enable
IIS Basic Authentication. When IIS Basic Authentication is enabled on
your windows machine, it prevents you from using personal access
tokens (PATs) as an authentication mechanism.
Source link.
Moreover, we do have a good integration with AWS. We could call AWS services from Azure DevOps/TFS side. Also be able to use AWSCLI & AWS Powershell Module. In case you need, for more details please take a look at this link-- AWS Tools for Microsoft Visual Studio Team Services/TFS

Authentication for Java Rally Rest API

I need to integrate my web application with Rally. Using Rest Rally API(Java), though I am able to create a User story from a stand alone program hardcoding my APIKey. But how do i create it using oauth authentication in java. I did get that through oauth authentication, we will get an access token. but how do i make use of that access token for using Rally rest API since, it accepts either API Key or userName/pwd for that. Any help on authentication will be appreciated. Thanking in advance.
String host = "https://rally1.rallydev.com";
RallyRestApi restApi = new RallyRestApi(new URI(host), "my_APIKey-XXXXXX");
// Creating a User Story
JsonObject newStory = new JsonObject();
newStory.addProperty("Name", "test User Story");
newStory.addProperty("Project", projectRef);
newStory.addProperty("Workspace",workspaceRef);
newStory.addProperty("Iteration", iterationRef);
newStory.addProperty("Release", releaseRef);
newStory.addProperty("Description", "Test Description");
newStory.addProperty("Notes", "test Notes");
newStory.addProperty("c_AcceptanceCriteria","Test acceptance criteria");
CreateRequest createRequest = new CreateRequest("hierarchicalrequirement", newStory);
The Rally Rest Toolkit for Java does not currently support OAuth/SSO. API Keys are the best way forward currently.

desktop client application for SSO using SAML

I need to write a desktop based client application which does some web service method calls to a SharePoint server, after doing a SAML based SSO authentication.
I found that SAML SSO is mostly used from the browser which takes care of all the details. According to this question, it seems there is a technology in SAML 2.0 called ECP just for enabling non browser based clients.
Yet some applications like SharePoint 2010/2013 only support SAML 1.1; what can be used in this case?
You haven't mentioned technology - i can share my experience.
We're required to have a SSO in the desktop application (WPF) that is using the WCF services. I have started with infomation from this link. The solution is to use WIF for retrieving the SAML token from identity provider and using it to establish the connection to our backend server.
To obtain the token
WSTrustChannelFactory GetTrustFactory()
{
var binding = new WS2007HttpBinding(TrustChannelBindingConfiguration);
return new WSTrustChannelFactory(binding, StServiceUri);
}
SecurityToken GetTokenFromSts()
{
using (var trustFactory = GetTrustFactory())
{
// here is the code to set trustFactory.Credentials
trustFactory.TrustVersion = TrustVersion.WSTrust13;
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointReference(YourServiceUri),
KeyType = KeyTypes.Bearer
};
var channel = (WSTrustChannel) trustFactory.CreateChannel();
try
{
return channel.Issue(rst);
}
catch (MessageSecurityException msex)
{
channel.Abort();
throw new EMException(msex.InnerException.Message, msex);
}
}
}
Then the obtained token is used in service calls:
securityToken = GetToken();
// 2. Create a channel with issued token to YourServiceInterface
// create binding and turn off sessions
var binding = new WS2007FederationHttpBinding(FederationBinding);
try
{
var factory = new ChannelFactory<YourServiceInterface>(binding,
new EndpointAddress(YourServiceUri));
factory.Credentials.SupportInteractive = false;
var channel = factory.CreateChannelWithIssuedToken(securityToken);
// 3. Call YourMethod() on secured channel
return channel.YourMethod();
}
catch {...}
The main approach from the link hasn't been really changed - we just added token caching and incorporated this code in our channel handling framework.
The code is used to authenticate desktop client against ADFS server and use claims in our backend server for authorizations.

Custom Basic Authentication of a Web service

I'm having a small issue with implementing custom basic authentication for a asmx in .net 4.0.
I've created the HttpModule which would start the authentication process of the web service consumer runnig this code,
HttpApplication application = (source as HttpApplication);
HttpContext context = application.Context;
if ( VirtualPathUtility.GetFileName(context.Request.FilePath).Contains("svcEWS.asmx"))
{
string username = "", password = "";
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Basic"))
{
//Authenticate here
}
}
However there is no authentication header present whenever this code is reached.
The consuming web app is simply calling,
EWS.svcEWS svcEWS = new EWS.svcEWS();
svcEWS.Credentials = new NetworkCredential("admin", "admin", "example.com");
svcEWS.HelloWorld();
IIS is set to run with anonymous authentication to anonymous authentication to prevent it from catching any auth requests.
Is there something I'm missing to have the client pass the correct header to my module?
I was forgetting to reject the first request with 401 code to force authentication. Doing so fixes the problem, as the invoker re sends the request with the auth header.