I have a continuously scheduled web job that's monitoring a message queue, pulling messages off and calling a Web API on the peer Web Site to process the messages (in this case using SignalR to send notifications to appropriate users).
What would be the best way in this case to call the web API securely? The API being hosted in the web site is obviously exposed otherwise. Perhaps something using Basic Auth or storing a security token in config and passing it from the job to the web API. Or creating a custom AuthorizeAttribute?
Ant thoughts on securing the Web API call from the WebJob would be much appreciated. The API should only be callable from the WebJob.
UPDATE:
Something like this perhaps?
First I declare this class;
public class TokenAuthenticationHeaderValue : AuthenticationHeaderValue
{
public TokenAuthenticationHeaderValue(string token)
: base("Token", Convert.ToBase64String(Encoding.UTF8.GetBytes(token)))
{ }
}
Then the caller (the WebJob) uses this class to set an auth header when making the HTTP request;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(/* something */);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new TokenAuthenticationHeaderValue("TOKEN FROM CONFIG");
// ....
Over in the Web API we check the request looking for the expected token in the auth header, currently the code is pretty ugly but this could be put into a custom attribute;
public HttpResponseMessage Post([FromBody]TheThing message)
{
var authenticationHeader = Request.Headers.Authorization;
var token = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationHeader.Parameter));
if (authenticationHeader.Scheme != "Token" || token != "TOKEN FROM CONFIG")
{
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "No, no, no. That's naughty!");
}
// All OK, carry on.
So this way the WebJob calls the Web API on the peer web site and security is achieved by passing a token that is securely held in the Azure configuration, both the Site and Job have access to this token.
Any better ideas?
Sounds like Basic Authentication would be fine for your scenario.
Great tutorial here: Basic Authentication
Related
I configured azure application proxy for our on-premise hosted web service and turned on Azure AD authentication. I am able to authenticate using ADAL but must find a way to get the token and call web service without ADAL now (we are going to use this from Dynamics 365 online and in sandbox mode I can't use ADAL). I followed some examples regarding service to service scenario and I successfully retrieve the token using client credentials grant flow. But when I try to call the app proxy with Authorization header and access token, I receive an error "This corporate app can't be accessed right now. Please try again later". Status code is 500 Internal server error.
Please note the following:
I don't see any error in app proxy connectors event log.
I added tracing on our on-premise server and it seems like the call never comes there.
If I generate token with ADAL for a NATIVE app (can't have client_secret so I can't use client credentials grant flow), I can call the service.
I created an appRole in manifest for service being called and added application permission to the client app.
This is the way I get the token:
public async static System.Threading.Tasks.Task<AzureAccessToken> CreateOAuthAuthorizationToken(string clientId, string clientSecret, string resourceId, string tenantId)
{
AzureAccessToken token = null;
string oauthUrl = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", tenantId);
string reqBody = string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&resource={2}", Uri.EscapeDataString(clientId), Uri.EscapeDataString(clientSecret), Uri.EscapeDataString(resourceId));
HttpClient client = new HttpClient();
HttpContent content = new StringContent(reqBody);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");
using (HttpResponseMessage response = await client.PostAsync(oauthUrl, content))
{
if (response.IsSuccessStatusCode)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AzureAccessToken));
Stream json = await response.Content.ReadAsStreamAsync();
token = (AzureAccessToken)serializer.ReadObject(json);
}
}
return token;
}
AzureAccessToken is my simple class marked for serialization.
I assume it must be something I haven't configured properly. Am I missing some permissions that are required for this scenario?
Any help is appriciated.
I have browsed all the tutorials regarding using Oauth to protect WebAPI in Azure active directory online. But unfortunately, none of them can work.
I am using VS 2017 and my project is .net core.
So far what I have tried is:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
ervices.AddAuthentication(); // -----------> newly added
}
In "Configure", I added:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]),
Audience = Configuration["AzureAd:Audience"],
});
Here is my config:
"AzureAd": {
"AadInstance": "https://login.microsoftonline.com/{0}",
"Tenant": "tenantname.onmicrosoft.com",
"Audience": "https://tenantname.onmicrosoft.com/webapiservice"
}
I have registered this "webapiservice" (link is: http://webapiservice.azurewebsites.net) on my AAD.
Also, to access this web api service, I created a webapi client "webapiclient" which is also a web api and also registered it on my AAD and requested permission to access "webapiservice". The webapi client link is: http://webapiclient.azurewebsites.net
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://webapiservice.azurewebsites.net/");
//is this uri correct? should it be the link of webapi service or the one of webapi client?
HttpResponseMessage response = client.GetAsync("api/values").Result;
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsAsync<IEnumerable<string>>().Result;
return result;
}
else
{
return new string[] { "Something wrong" };
}
So theoretically, I should receive the correct results from webapiservice. but I always received "Something wrong".
Am I missing anything here?
You need an access token from Azure AD.
There are plenty of good example apps on GitHub, here is one for a Daemon App: https://github.com/Azure-Samples/active-directory-dotnet-daemon/blob/master/TodoListDaemon/Program.cs#L96
AuthenticationResult authResult = await authContext.AcquireTokenAsync(todoListResourceId, clientCredential);
This app fetches an access token with its client id and client secret for an API. You can follow a similar approach in your case. You can just replace todoListResourceId with "https://graph.windows.net/" for Azure AD Graph API, or "https://graph.microsoft.com/" for Microsoft Graph API, for example. That is the identifier for the API that you want a token for.
This is the way it works in AAD. You want access to an API, you ask for that access from AAD. In a successful response you will get back an access token, that you must attach to the HTTP call as a header:
Authorization: Bearer accesstokengoeshere......
Now if you are building a web application, you may instead want to do it a bit differently, as you are now accessing the API as the client app, not the user. If you want to make a delegated call, then you will need to use e.g. the Authorization Code flow, where you show the user a browser, redirect them to the right address, and they get sent back to your app for login.
To call web api protected by azure ad , you should pass this obtained access token in the authorization header using a bearer scheme :
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
I know that local debugging using tokens is possible using http://www.systemsabuse.com/2015/12/04/local-debugging-with-user-authentication-of-an-azure-mobile-app-service/. Would it be possible to go to thesite.com/.auth/login/aad and login and use that cookie for localhost (for testing the web app - not the mobile app)?
I am currently using the .auth/login/aad cookie to authenticate Nancy. I do by generating a ZumoUser out of the Principal.
Before.AddItemToEndOfPipeline(UserToViewBag);
and
internal static async Task<Response> UserToViewBag(NancyContext context, CancellationToken ct)
{
var principal = context.GetPrincipal();
var zumoUser = await ZumoUser.CreateAsync(context.GetPrincipal());
context.ViewBag.User = zumoUser;
context.Items["zumoUser"] = zumoUser;
var url = context.Request.Url;
if (zumoUser.IsAuthenticated)
{
_logger.DebugFormat("{0} requested {1}", zumoUser, url.Path);
}
else
{
_logger.DebugFormat("{0} requested {1}", "Anonymous", url.Path);
}
return null;
}
Yes. You need to read "the book" as it is a complex subject. The book is available open source at http://aka.ms/zumobook and the content you want is in Chapter 2.
Would it be possible to go to thesite.com/.auth/login/aad and login and use that cookie for localhost (for testing the web app - not the mobile app)?
No, this is impossible. The JWT token verification is based on the stand protocol(OpenId connect or Oauth 2) we can follow. But there is no official document or SDK about the the cookie issued by the Easy Auth verification.
Lets say I have a web client (i.e. MVC 4 client) that authenticates users using an oAuth provider (i.e. Facebook, Google etc).
I want to call another web service in my client logic, and that web service also authenticates with oAuth providers.
What would the web service request look like from the client? What do I need to pass to the web service?
I suggest you review this question, How do I authorize access to ServiceStack resources using OAuth2 access tokens via DotNetOpenAuth?. The poster provided his final solution, including a link to a sample solution, which he has graciously open sourced. The client side code, for his solution, looks like this:
// Create the ServiceStack API client and the request DTO
var apiClient = new JsonServiceClient("http://api.mysite.com/");
var apiRequestDto = new Shortlists { Name = "dylan" };
// Wire up the ServiceStack client filter so that DotNetOpenAuth can
// add the authorization header before the request is sent
// to the API server
apiClient.LocalHttpWebRequestFilter = request => {
// This is the magic line that makes all the client-side magic work :)
ClientBase.AuthorizeRequest(request, accessTokenTextBox.Text);
}
// Send the API request and dump the response to our output TextBox
var helloResponseDto = apiClient.Get(apiRequestDto);
Console.WriteLine(helloResponseDto.Result);
A similar solution is provided here: https://stackoverflow.com/a/13791078/149060 which demonstrates request signing as per OAuth 1.0a
var client = new JsonServiceClient (baseUri);
client.LocalHttpWebRequestFilter += (request) => {
// compute signature using request and a previously obtained
// access token
string authorization_header = CalculateSignature (request, access_token);
request.Headers.Add ("Authorization", authorization_header);
};
var response = client.Get<MySecuredResponse> ("/my/service");
You will, of course, need to adjust to fit the requirements of your OAuth providers, i.e. signing, token, etc.
I've a wcf service deployed to the cloud. Could anyone guuide me through best practices on how I can secure the end point in azure please?
Thanks.
In my opinion, the easiest approach is to use the AppFabric Access Control Service (ACS) to generate a Secure Web Token (SWT) that you pass to the WCF service via an authorization HTTP header. In the service method, you can then read and validate the SWT from the header.
It's pretty straightforward, particularly if you create proxies dynamically rather than using Service References.
This is how I get the SWT from ACS:
private static string GetToken(string serviceNamespace, string issuerKey, string appliesto)
{
WebClient client = new WebClient();
client.BaseAddress = String.Format("https://{0}.accesscontrol.windows.net", serviceNamespace);
client.UseDefaultCredentials = true;
NameValueCollection values = new NameValueCollection();
values.Add("wrap_name", serviceNamespace);
values.Add("wrap_password", issuerKey);
values.Add("wrap_scope", appliesto);
byte[] responseBytes = client.UploadValues("WRAPv0.9", "POST", values);
string response = System.Text.Encoding.UTF8.GetString(responseBytes);
string token = response
.Split('&')
.Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
.Split('=')[1];
return token;
}
issuerKey, as it was referred to in ACS v1 is now the Password from the Service Identity in ACS v2.
To call the service:
string accessToken = GetToken(serviceNamespace, issuerKey, appliesto);
string authHeaderValue = string.Format("WRAP access_token=\"{0}\"", HttpUtility.UrlDecode(accessToken));
// TInterface is the service interface
// endpointName refers to the endpoint in web.config
ChannelFactory channelFactory = new ChannelFactory<TInterface>(endpointName);
TInterface proxy = channelFactory.CreateChannel();
OperationContextScope scope = new OperationContextScope(proxy as IContextChannel);
WebOperationContext.Current.OutgoingRequest.Headers.Add(HttpRequestHeader.Authorization, authHeaderValue);
// Call your service
proxy.DoSomething();
On the service-side, you extract the token from the header and validate it. I can find out the code for that, if this looks like the approach you want to take.
Try this blog post by Alik Levin as a good starting point.
A typical, broadly interoperable approach would be to use HTTP Basic Authentication over an SSL connection. The approach for running this in Azure is really very similar to how you would achieve this on a traditional Windows server.
You can implement an IIS Http Module and provide your own implementation of a BasicAuthenticationModule - this can work however you want but calling in to ASP.NET Membership (a call to ValidateUser) would be a common approach. The store for that can be hosted in SQL Azure.
You can then surface this to WCF by implementing IAuthorizationPolicy and adding this to your authorizationPolicies WCF config element.
The Patterns and Practices team have a walkthrough of this with complete code at
http://msdn.microsoft.com/en-us/library/ff649647.aspx. You can ignore the brief Windows Forms discussion - being web services, their choice of client is irrelevant.