I have a WebAPI built using ASP.NET MVC4. It is a simple API for getting data (simple HTTP GET requests). The API is stable and has been working with our mobile (MonoTouch) app for quite some time. Now we're putting ForeFront UAG in front of the API (simply changed web.config to use windows auth. Testing the security and API through a browser e.g. Chrome, and the UAG login is presented (when hitting API first time). Enter your credentials and then you get the data back for the API GET request. All what you'd expect. Now, from .NET code (no browser) I want to do the same thing. I've seen examples accessing SharePoint programmatically and some windows phone stuff, but none of them seem to work for ASP.NET MVC4 WebApi calls from just regular old .NET code (which I'll eventually use in MonoTouch).
Anyone have an example of how to Authenticate and then make HTTP GET request successfully through UAG to an ASP.NET MVC4 WebApi?
I don't have the disposal over a ForeFront UAG so I can't test this. But in general you have a few options. The samples are snippets and some code is left out for readability.
WebClient / HttpWebRquest
CredentialCache credentials = new CredentialCache();
credentials.Add(new Uri(url), "NTLM", new NetworkCredential(userName, password, domain));
//WebClient
var webClient = new WebClient();
webClient.Credentials = credentials;
//HttpWebRequest
var request = HttpWebRequest.Create(url);
request.Credentials = credentials
HttpClient
WebRequestHandler clientHandler = new WebRequestHandler();
clientHandler.UseDefaultCredentials = true;
clientHandler.AllowPipelining = true;
clientHandler.ImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
ProgressMessageHandler progress = new ProgressMessageHandler();
httpClient = HttpClientFactory.Create(clientHandler, progress);
You have also the options of using third party libraries to get this job done, like RestSharp or Service Stack.
Personally I make use of RestSharp because of the ease of use and serializing/deserializing capabilities.
Related
I have an issue over Google OAuth and Chrome Web Store APis.
Some context before starting with the tech stuff.
I have a popular Chrome Extension that delivers in-app purchases: purchasing an item the extension goes "pro", so usage limits are expanded.
Last year the audience asked me to make a porting on Firefox (thanks to the newly available WebExtentions APIs), and I started delivering both versions.
The Firefox version does not support in-app purchase at this moment, but people are starting to ask for it (let's think the FF version as an alpha version, so not all features are guarantee to be supported).
Problem: That said, I don't want to implement a secondary payment service using Google Pay APIs or other similar APIs and I'd like that if a user makes an in-app purchase on Chrome, it is delivered on FF as well.
Hoped Solution: I thought it would have been easy to use the Google Chrome Web Store APIs. Making an user authorising its Google Account from the FF add-on I could understand if it was a paying user or not, thus enabling or disabling the "pro" limits.
Here are the steps I did.
Step 1 - Create a new app on the Google Cloud Console (I already have the one used on the Chrome Extension):
And enable the Chrome Web Store APIs:
Step 2 - Jump on Firefox JS console (inside the Options page of the add-on, so I have all the WebExtensions APIs enabled), and call the method to get a valid token (the code is quick and dirty, not enhanced and written just as a POC):
var scopes = ['https://www.googleapis.com/auth/plus.login',
'https://www.googleapis.com/auth/chromewebstore.readonly'];
var url = 'https://accounts.google.com/o/oauth2/v2/auth?'
+ 'client_id=xxxxxxxx.apps.googleusercontent.com'
+ '&response_type=token'
+ '&scope='
+encodeURIComponent(scopes.join(' '))
+ '&redirect_uri='
+encodeURIComponent(browser.identity.getRedirectURL());
browser.identity.launchWebAuthFlow(
{interactive: true, url: url})
.then(function(result){
console.log(result);
})
.catch(function(err){
console.log(err);
});
Using the browser.identity.launchWebAuthFlow() function I got a valid token from the returning result (in the query string on the access_token parameter).
Now I get call the Chrome WebStore APIs with no issue (I thought) with this simple code:
//access token
var token = 'previous_access_token';
//Google Chrome immutable Ext. ID from the store
var GOOGLE_CHROME_EXTENSION_ID = 'XXXXYYYYZZZ....';
//endpoint to get all purchased items (see https://developer.chrome.com/webstore/webstore_api/payments#resource)
var CWS_LICENSE_API_URL = 'https://www.googleapis.com/chromewebstore/v1.1/items/';
var req = new XMLHttpRequest();
req.open('GET', CWS_LICENSE_API_URL+ GOOGLE_CHROME_EXTENSION_ID + '/payments');
req.setRequestHeader('Authorization', 'Bearer ' + token);
req.onreadystatechange = function() {
if (req.readyState == 4) {
var license = JSON.parse(req.responseText);
console.log(license);
}
}
req.send();
Unfortunately this doesn't work at all, getting the following error:
(You don't have access to licensing data for App ID: xxxxyyyzzzzz)
I have the following evidences:
If I use a token got from the Chrome Extension using chrome.identity.getAuthToken() the call is ok
If I run the same script inside the Options page of the Chrome Extension, I get the same error
A specific Chrome WebStore API actually work (the get items API)
I guess for some reason Chrome WebStore APIs are not accessible using a token produced outside a specific context (i.e. Chrome extension subject of the call).
Does anyone have evidence of this? Google team are you there :) ?
I'm trying to use the sharepoint online search api (/_api/search) from an application. Adding it from the Azure portal I see the search is only in the "delegated permissions" section. When I ran it in testing with the user login and approve it it works well.
Since I don't want to need a user to login for this, I found this article
https://blogs.msdn.microsoft.com/vesku/2016/03/07/using-add-in-only-app-only-permissions-with-search-queries-in-sharepoint-online/
That made me believe it would be possible to use search as an app-only and not as a user. I followed all the steps, created the app through appregnew.aspx , I also added another permission via appinv.aspx so the permissions I asked for are the following :
<AppPermissionRequests AllowAppOnlyPolicy="true">
<AppPermissionRequest Scope="http://sharepoint/content/tenant" Right="FullControl" />
<AppPermissionRequest Scope="http://sharepoint/search" Right="QueryAsUserIgnoreAppPrincipal" />
</AppPermissionRequests>
I'm testing using ADAL JAVA SDK as follows:
Future<AuthenticationResult> future = context.acquireToken(
resource, new ClientCredential(clientId,
clientSecret), null);
where resource is xxxxxx.sharepoint.com and I'm later using this token as the bearer token.
But when I'm trying to test this I get the following error:
2018-08-05 11:03:22 WARN ODataUtils:120 - Failed to get a successful response for uri [https://XXXXXX.sharepoint.com/_api/search], reason [{"error_description":"The server was unable to process the request due to an internal error. For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework SDK documentation and inspect the server trace logs."}];
Since this is sharepoint online I don't have a server and I'm not using the .NET framework so what other way do I have to debug this? or other idea what I'm doing wrong here?
Any help would be greatly appreciated.
Maybe you can use the Java to call SharePoint Search Api and show the result(But we need to spend many time to research on this, there are many uncertainties.)
The best choice for you is to use the VisualStudio to test the SharePoint Addin. Microsoft provide more support on it and you can use the ready-made template.
You can use the wide range of search-related APIs that SharePoint offers for search add-ins:
.NET client object model (CSOM)
Key libs:
Microsoft.SharePoint.Client.Search.dll ;
Silverlight CSOM
Key libs:
Microsoft.SharePoint.Client.Search.Silverlight.dll ;
ECMAScript (JavaScript, JScript) object model (JSOM)
Key libs:
SP.search.js ;
Search REST API
http://server/_api/search/query
Some demo code:
Client-side Object Model (CSOM)
C#
using (ClientContext clientContext = new ClientContext("http://localhost"))
{
KeywordQuery keywordQuery = new KeywordQuery(clientContext);
keywordQuery.QueryText = "*";
SearchExecutor searchExecutor = new SearchExecutor(clientContext);
ClientResult<ResultTableCollection> results =
searchExecutor.ExecuteQuery(keywordQuery);
clientContext.ExecuteQuery();
}
JavaScript Object Model (JSOM)
var keywordQuery = new
Microsoft.SharePoint.Client.Search.Query.KeywordQuery(context);
keywordQuery.set_queryText('SharePoint');
var searchExecutor = new Microsoft.SharePoint.Client.Search.Query.SearchExecutor(context);
results = searchExecutor.executeQuery(keywordQuery);
context.executeQueryAsync(onQuerySuccess, onQueryFail);
REST
HTTP GET request
HTML
http://mylocalhost/_api/search/query?querytext='SharePoint'
HTTP POST request
HTML
{
'__metadata' : {'type' : 'Microsoft.Office.Server.Search.REST.SearchRequest'},
'Querytext' : 'SharePoint'
}
Then set the permissions by VisualStudio and "Napa" Office 365 Development Tools
More information on Search add in:
https://learn.microsoft.com/en-us/sharepoint/dev/general-development/search-add-ins-in-sharepoint
We have a Single Page App (SPA) that uses Azure Active Directory "Easy Auth", e.g., the code-less solution. This seems to work ok when users first open the the application. They are redirected to the Microsoft login page and they can authenticate and then access the application.
Then, because its an SPA, users will navigate around and only fire Ajax requests. The problems come approximately 24 hours later when the session cookie expires. Users likely still have the same browser tab open and do not perform a full page refresh. Then they may be working on a record and at some point their next Ajax PUT request fails with a Redirect HTTP status and they loose their work.
So they key question is:
How can we make SPA Ajax requests extend a current user's session so that their session will not expire when they are actively using the application?
It seems like the Azure AD Easy Auth service does not "honor" activity on the part of the user, which leads us to believe that the session cookie never gets updated.
Note: We've recently done some testing with the /.auth/refresh endpoint and this does not solve the problem either.
There are several ways you can possibly solve this. Here are a few that I can think of:
Use local storage: The problem you mentioned is that user's lose their work due to the redirects. The problem of losing work can be solved if you persist the in-progress state in local storage so that it's available when they are redirected back to the page.
Switch to using tokens: The /.auth/refresh endpoint doesn't refresh the AppServiceAuthSession when using AAD because AAD doesn't support refreshing the user information. What you can do instead is authenticate with your backend using the x-zumo-auth tokens. The /.auth/refresh endpoint will correctly refresh these tokens. If you're explicitly logging in users using /.auth/login/aad, then you can add the session_mode=token as a query string parameter. This is done for you if you use the Mobile Apps JavaScript SDK. If login is automatic, then you'll need to add session_mode=token in the additionalLoginParams setting of your auth config. You can then parse the authentication token from the #token fragment which is added to the URL after the login completes.
Use hidden iframes: I haven't tried this myself, but if you can get it working it might require the least amount of code change. The idea is that you use a hidden iframe to re-login the user periodically when you detect they are active. The iframe would need to point to something like ./auth/login/aad?prompt=none&domain_hint={userdomain.com} where {userdomain.com} is the last part of the user's email address - e.g. contoso.com. These parameters get passed to the AAD login page, and the login should complete automatically without any user interaction. Test it manually a few times in a browser window to make sure it works correctly. The result should be an updated auth cookie with a fresh expiration.
Let me know in the comments if you have any questions or issues with any of these options.
Expanding on Chris Gillum's answer with implementation example:
Scenario: Single Page Application (SPA) with Progressive Web App (PWA) capabilities, hosted in Azure Web App. Added authentication using Azure Web Authentication/EasyAuth.
Ran into similar/same issue: Initial loads of the SPA worked fine, but after period of hour(s) (token expires) the app "breaks" - in SPA on iOS tablet that manifested for me with endless whitescreen and seemingly no practical fix (force killing did NOT resolve). Error messages thrown ranged from 401 (understandable) to service-worker refusing to process scripts/handle 302 redirects/etc (less obvious where problem may be).
SPA + Azure Web Authentication/EasyAuth tweaks:
If using MDM, disable "Block Safari navigation menu bar" feature in the MDM for this app. This appears to allow the app to work as expected after force kill (it would reload the page, see expired token, redirect to login and then back to the app). I'm not sure if this behavior is controllable in manifest.json, may be iOS specific capability.
Hidden iframe refreshing of token + Timer/check token periodically (in ajax calls, etc):
Note: As of ~2021-04, Chromium based browser worked with hidden iframe method. For other browsers the AAD page would experience errors and fail - current solution suggested would be storing app state -> navigate to AAD login page with redirect param -> User logs in and redirected back to the app -> App state restored w/ refreshed token.
refreshAuthToken() {
//Chrome based browsers work with silent iFrame based token reAuth
if (this.browserChromium()) {
let domainHint = "contoso.com"; //Domain of your organization users (e.g. me#contoso.com)
//Remove existing iframe (if exists), to minimize history/back button entries
let existingFrame = document.getElementById("authIFrame");
if (existingFrame) {
existingFrame.remove();
}
//Inject iFrame that will call endpoint to refresh token/cookie
console.log("Refreshing auth token (quietly)...");
let iframe = document.createElement("iframe");
iframe.id = "authIFrame";
iframe.style =
"width: 0; height: 0; border: 0; border: none; position: absolute; visibility: hidden;";
iframe.src = `/.auth/login/aad?prompt=none&domain_hint=${domainHint}`;
document.body.appendChild(iframe);
new Promise(r => setTimeout(r, 2000)).finally(() => resolve()); //Hacky method of "waiting" for iframe to finish
} else {
console.log("Refreshing auth token (via page reload)...");
window.location.replace("/.auth/login/aad?post_login_redirect_url=/?restoreData=true");
}
},
//
// Timer example:
//
setInterval(() => {this.refreshAuthToken()}, 1000 * 60 * 5); //Fire every 5 minutes
//
// And/or periodically call this function maintain token freshness
//
checkAuthToken() {
//this.authEnd = JWT from /.auth/me "exp" claim
let now = new Date() / 1000;
let expirationWindow = this.authEnd - 600; // Consider token expiring if 10 minutes or less remaining
if (now >= expirationWindow) {
console.log("Auth Token expired - Refreshing...")
this.refreshAuthToken();
} else {
// console.log("Auth token still healthy.");
}
}
Nicety: Enable anonymous access to PWA icons (if possible). iOS requires icons be publicly accessible when saving PWA to homescreen, otherwise uses screenshot of app rather than formal icon: https://stackoverflow.com/a/67116374/7650275
I'm trying to implement a custom authentication in an Azure Mobile App (not the old Mobile Service) with a Node.js backend, with actions I can't quite translate into Node. An earlier question states that custom authentication "just works" with a .NET backend. I am having trouble getting
I have copied Joy of code's example JWT generation (gist here). I invoke it like this (inlining the aud and userId):
zumoJWT(expiry,"MyAud","MyAud:1455527189540927",req.azureMobile.configuration.auth.secret);
My registration API returns the following JSON
{"user":{"userid":"MyAud:1455527189540927"},"token":"a lot of base64"}
Which I put into the Android MobileServiceClient with this code
JsonObject userob=ob.get("user").getAsJsonObject();
MobileServiceUser user=new MobileServiceUser(userob.get("userid").getAsString());
user.setAuthenticationToken(ob.get("token").getAsString());
mClient.setCurrentUser(user);
Which gives me the error message
com.microsoft.windowsazure.mobileservices.MobileServiceException: {"name":"JsonWebTokenError","message":"invalid signature"
The next time I invoke an API. How do I make my app accept the login token?
Edit: The server-side logs say
2016-02-15T11:42:35 PID[180] Warning JWT validation failed: IDX10500: Signature validation failed. Unable to resolve SecurityKeyIdentifier: 'SecurityKeyIdentifier
(
IsReadOnly = False,
Count = 1,
Clause[0] = System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause
)
',
token: '{"alg":"HS256","typ":"JWT","kid":0}.{"exp":null,"iss":"urn:microsoft:windows-azure:zumo","ver":2,"aud":"MyAud","uid":"MyAud:1455534835642715"}
RawData: a lot of base64'..
I figured it out. I needed to have
mobile.configuration.auth.validateTokens=false;
in app.js (or rather, not have the same variable set to true).
As a follow up to a previous question I asked: How to pass username and password in TeamCity REST API, I'd like to check on something.
Can someone tell me if it's possible to access the TeamCity REST API in a more secure way, rather then passing the username and password in the url?
It just seems crazy to me that passing credentials in the url is the only way, since it's so easy for a sniffer to get their hands on the url and use the credentials themselves.
We faced the same problem and I spent some time to see how could we solve this problem and found a way:
You do a get in the initial screen (/ntlmLogin.html) - you'll be able to identify the user using NTLM.
Then you save the cookie that TeamCity provides to you.
Now you use the cookie to reach the API.
See https://github.com/eduaquiles/TeamCityNtlmApiWrapper with a very simple example on how to do this.
I've done some more digging around with this and it does not look too promising.
I found the following thread on the TeamCity community forums:
Rest API Authentication Integrated
http://devnet.jetbrains.net/message/5461520#5461520
Another user had asked a similar question to mine and the response was that basic HTTP authentication is currently the only option. Although you can use NTLM authentication, that is tailored towards the front end web UI, not the REST API.
I have asked on the forum whether using NTLM via the REST API is possible. I've not had a reply, but I can imagine that it's not possible, which would be expected in this case.
As per Eduardo Aquiles, if you configure your TeamCity server to support HTTP NTLM authentication (TeamCity 8.x NTLM HTTP Authentication), you can get a session cookie (TCSESSIONID) from the /ntlmLogin.html url and use that to authenticate against the REST API.
I've just had to do something similar to get the pinned state of builds. Here's the PowerShell I used:
function Get-TeamCityNtlmAuthCookie()
{
param( [string] $serverUrl )
$url = "$serverUrl/ntlmLogin.html";
$cookies = new-object System.Net.CookieContainer;
$request = [System.Net.WebRequest]::Create($url);
$request.CookieContainer = $cookies;
$request.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;
$request.PreAuthenticate = $true;
$response = $request.GetResponse();
return $cookies;
}
function Get-TeamCityBuildPinnedState()
{
param( [string] $serverUrl, [string] $buildTypeId)
# get a session cookie to use with the rest api
$cookies = Get-TeamCityNtlmAuthCookie $serverUrl;
# query the rest api using the session cookie for authentication
$url = "$serverUrl/httpAuth/app/rest/builds/id:$buildTypeId/pin/";
$request = [System.Net.WebRequest]::Create($url);
$request.CookieContainer = $cookies;
$response = $request.GetResponse();
$stream = $response.GetResponseStream();
$reader = new-object System.IO.StreamReader($stream);
$text = $reader.ReadToEnd();
$reader.Close();
return [bool]::Parse($text);
}
$myServerUrl = "http://myTeamCityServer";
$myBuildId = "6";
$pinned = Get-TeamCityBuildPinnedState $myServerUrl $myBuildId;
write-host $pinned;
Note: I'm not sure if this is officially supported by JetBrains, so you might find it breaks in a future version of TeamCity, but it currently works against version 8.0.2 (build 27482).