How to secure an ASP.NET Web API [closed] - c#

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I want to build a RESTful web service using ASP.NET Web API that third-party developers will use to access my application's data.
I've read quite a lot about OAuth and it seems to be the standard, but finding a good sample with documentation explaining how it works (and that actually does work!) seems to be incredibly difficult (especially for a newbie to OAuth).
Is there a sample that actually builds and works and shows how to implement this?
I've downloaded numerous samples:
DotNetOAuth - documentation is hopeless from a newbie perspective
Thinktecture - can't get it to build
I've also looked at blogs suggesting a simple token-based scheme (like this) - this seems like re-inventing the wheel but it does have the advantage of being conceptually fairly simple.
It seems there are many questions like this on SO but no good answers.
What is everybody doing in this space?

Update:
I have added this link to my other answer how to use JWT authentication for ASP.NET Web API here for anyone interested in JWT.
We have managed to apply HMAC authentication to secure Web API, and it worked okay. HMAC authentication uses a secret key for each consumer which both consumer and server both know to hmac hash a message, HMAC256 should be used. Most of the cases, hashed password of the consumer is used as a secret key.
The message normally is built from data in the HTTP request, or even customized data which is added to HTTP header, the message might include:
Timestamp: time that request is sent (UTC or GMT)
HTTP verb: GET, POST, PUT, DELETE.
post data and query string,
URL
Under the hood, HMAC authentication would be:
Consumer sends a HTTP request to web server, after building the signature (output of hmac hash), the template of HTTP request:
User-Agent: {agent}
Host: {host}
Timestamp: {timestamp}
Authentication: {username}:{signature}
Example for GET request:
GET /webapi.hmac/api/values
User-Agent: Fiddler
Host: localhost
Timestamp: Thursday, August 02, 2012 3:30:32 PM
Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw=
The message to hash to get signature:
GET\n
Thursday, August 02, 2012 3:30:32 PM\n
/webapi.hmac/api/values\n
Example for POST request with query string (signature below is not correct, just an example)
POST /webapi.hmac/api/values?key2=value2
User-Agent: Fiddler
Host: localhost
Content-Type: application/x-www-form-urlencoded
Timestamp: Thursday, August 02, 2012 3:30:32 PM
Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw=
key1=value1&key3=value3
The message to hash to get signature
GET\n
Thursday, August 02, 2012 3:30:32 PM\n
/webapi.hmac/api/values\n
key1=value1&key2=value2&key3=value3
Please note that form data and query string should be in order, so the code on the server get query string and form data to build the correct message.
When HTTP request comes to the server, an authentication action filter is implemented to parse the request to get information: HTTP verb, timestamp, uri, form data and query string, then based on these to build signature (use hmac hash) with the secret key (hashed password) on the server.
The secret key is got from the database with the username on the request.
Then server code compares the signature on the request with the signature built; if equal, authentication is passed, otherwise, it failed.
The code to build signature:
private static string ComputeHash(string hashedPassword, string message)
{
var key = Encoding.UTF8.GetBytes(hashedPassword.ToUpper());
string hashString;
using (var hmac = new HMACSHA256(key))
{
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
hashString = Convert.ToBase64String(hash);
}
return hashString;
}
So, how to prevent replay attack?
Add constraint for the timestamp, something like:
servertime - X minutes|seconds <= timestamp <= servertime + X minutes|seconds
(servertime: time of request coming to server)
And, cache the signature of the request in memory (use MemoryCache, should keep in the limit of time). If the next request comes with the same signature with the previous request, it will be rejected.
The demo code is put as here:
https://github.com/cuongle/Hmac.WebApi

I would suggest starting with the most straightforward solutions first - maybe simple HTTP Basic Authentication + HTTPS is enough in your scenario.
If not (for example you cannot use https, or need more complex key management), you may have a look at HMAC-based solutions as suggested by others. A good example of such API would be Amazon S3 (http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html)
I wrote a blog post about HMAC based authentication in ASP.NET Web API. It discusses both Web API service and Web API client and the code is available on bitbucket. http://www.piotrwalat.net/hmac-authentication-in-asp-net-web-api/
Here is a post about Basic Authentication in Web API: http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/
Remember that if you are going to provide an API to 3rd parties, you will also most likely be responsible for delivering client libraries. Basic authentication has a significant advantage here as it is supported on most programming platforms out of the box. HMAC, on the other hand, is not that standardized and will require custom implementation. These should be relatively straightforward but still require work.
PS. There is also an option to use HTTPS + certificates. http://www.piotrwalat.net/client-certificate-authentication-in-asp-net-web-api-and-windows-store-apps/

Have you tried DevDefined.OAuth?
I have used it to secure my WebApi with 2-Legged OAuth. I have also successfully tested it with PHP clients.
It's quite easy to add support for OAuth using this library. Here's how you can implement the provider for ASP.NET MVC Web API:
1) Get the source code of DevDefined.OAuth: https://github.com/bittercoder/DevDefined.OAuth - the newest version allows for OAuthContextBuilder extensibility.
2) Build the library and reference it in your Web API project.
3) Create a custom context builder to support building a context from HttpRequestMessage:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Web;
using DevDefined.OAuth.Framework;
public class WebApiOAuthContextBuilder : OAuthContextBuilder
{
public WebApiOAuthContextBuilder()
: base(UriAdjuster)
{
}
public IOAuthContext FromHttpRequest(HttpRequestMessage request)
{
var context = new OAuthContext
{
RawUri = this.CleanUri(request.RequestUri),
Cookies = this.CollectCookies(request),
Headers = ExtractHeaders(request),
RequestMethod = request.Method.ToString(),
QueryParameters = request.GetQueryNameValuePairs()
.ToNameValueCollection(),
};
if (request.Content != null)
{
var contentResult = request.Content.ReadAsByteArrayAsync();
context.RawContent = contentResult.Result;
try
{
// the following line can result in a NullReferenceException
var contentType =
request.Content.Headers.ContentType.MediaType;
context.RawContentType = contentType;
if (contentType.ToLower()
.Contains("application/x-www-form-urlencoded"))
{
var stringContentResult = request.Content
.ReadAsStringAsync();
context.FormEncodedParameters =
HttpUtility.ParseQueryString(stringContentResult.Result);
}
}
catch (NullReferenceException)
{
}
}
this.ParseAuthorizationHeader(context.Headers, context);
return context;
}
protected static NameValueCollection ExtractHeaders(
HttpRequestMessage request)
{
var result = new NameValueCollection();
foreach (var header in request.Headers)
{
var values = header.Value.ToArray();
var value = string.Empty;
if (values.Length > 0)
{
value = values[0];
}
result.Add(header.Key, value);
}
return result;
}
protected NameValueCollection CollectCookies(
HttpRequestMessage request)
{
IEnumerable<string> values;
if (!request.Headers.TryGetValues("Set-Cookie", out values))
{
return new NameValueCollection();
}
var header = values.FirstOrDefault();
return this.CollectCookiesFromHeaderString(header);
}
/// <summary>
/// Adjust the URI to match the RFC specification (no query string!!).
/// </summary>
/// <param name="uri">
/// The original URI.
/// </param>
/// <returns>
/// The adjusted URI.
/// </returns>
private static Uri UriAdjuster(Uri uri)
{
return
new Uri(
string.Format(
"{0}://{1}{2}{3}",
uri.Scheme,
uri.Host,
uri.IsDefaultPort ?
string.Empty :
string.Format(":{0}", uri.Port),
uri.AbsolutePath));
}
}
4) Use this tutorial for creating an OAuth provider: http://code.google.com/p/devdefined-tools/wiki/OAuthProvider. In the last step (Accessing Protected Resource Example) you can use this code in your AuthorizationFilterAttribute attribute:
public override void OnAuthorization(HttpActionContext actionContext)
{
// the only change I made is use the custom context builder from step 3:
OAuthContext context =
new WebApiOAuthContextBuilder().FromHttpRequest(actionContext.Request);
try
{
provider.AccessProtectedResourceRequest(context);
// do nothing here
}
catch (OAuthException authEx)
{
// the OAuthException's Report property is of the type "OAuthProblemReport", it's ToString()
// implementation is overloaded to return a problem report string as per
// the error reporting OAuth extension: http://wiki.oauth.net/ProblemReporting
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
RequestMessage = request, ReasonPhrase = authEx.Report.ToString()
};
}
}
I have implemented my own provider so I haven't tested the above code (except of course the WebApiOAuthContextBuilder which I'm using in my provider) but it should work fine.

Web API introduced an Attribute [Authorize] to provide security. This can be set globally (global.asx)
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new AuthorizeAttribute());
}
Or per controller:
[Authorize]
public class ValuesController : ApiController{
...
Of course your type of authentication may vary and you may want to perform your own authentication, when this occurs you may find useful inheriting from Authorizate Attribute and extending it to meet your requirements:
public class DemoAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (Authorize(actionContext))
{
return;
}
HandleUnauthorizedRequest(actionContext);
}
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
throw new HttpResponseException(challengeMessage);
}
private bool Authorize(System.Web.Http.Controllers.HttpActionContext actionContext)
{
try
{
var someCode = (from h in actionContext.Request.Headers where h.Key == "demo" select h.Value.First()).FirstOrDefault();
return someCode == "myCode";
}
catch (Exception)
{
return false;
}
}
}
And in your controller:
[DemoAuthorize]
public class ValuesController : ApiController{
Here is a link on other custom implemenation for WebApi Authorizations:
http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/

If you want to secure your API in a server to server fashion (no redirection to website for 2 legged authentication). You can look at OAuth2 Client Credentials Grant protocol.
https://dev.twitter.com/docs/auth/application-only-auth
I have developed a library that can help you easily add this kind of support to your WebAPI. You can install it as a NuGet package:
https://nuget.org/packages/OAuth2ClientCredentialsGrant/1.0.0.0
The library targets .NET Framework 4.5.
Once you add the package to your project, it will create a readme file in the root of your project. You can look at that readme file to see how to configure/use this package.
Cheers!

in continuation to # Cuong Le's answer , my approach to prevent replay attack would be
// Encrypt the Unix Time at Client side using the shared private key(or user's password)
// Send it as part of request header to server(WEB API)
// Decrypt the Unix Time at Server(WEB API) using the shared private key(or user's password)
// Check the time difference between the Client's Unix Time and Server's Unix Time, should not be greater than x sec
// if User ID/Hash Password are correct and the decrypted UnixTime is within x sec of server time then it is a valid request

Related

Custom Token Service for issuing and validating JWT

We need to develop a "single sign on" service (SSO) to issue JWT for numerous amount of clients. Developers of these clients will also need an ability to validate these tokens. Obviously we can't provide them with our secret key we used to generate these tokens. So instead we decided to provide them an API service with two methods. One for issuing token and the second one to validate it.
I'm questioning myself if we're going for the right approach. Here is a basic scheme which shows how users will be working with their clients (secure applications)
User signs on with his credentials via our service and gets his access token.
Then his token is used in request headers of the secure application. SSO client module is AuthenticationHandler which sends HTTP requests to our service to check validity of the token.
Here is some code from SSO client module we use to validate the token. We use custom authentication handler which makes calls to the remote SSO service:
internal class SsoAuthenticationHandler : AuthenticationHandler<SsoAuthenticationOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!TryRetrieveToken(Request, out var token))
{
return AuthenticateResult.NoResult();
}
if (await _ssoClient.ValidateTokenAsync(token))
{
return AuthenticateResult.Success(...);
}
return AuthenticateResult.NoResult();
}
}
and the SsoClient iself:
public class SsoClient
{
public async Task<bool> ValidateTokenAsync(string token)
{
const string validateUrl = "api/auth/validatetoken";
var address = $"https://{_ssoHost}/{validateUrl}";
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var res = await httpClient.GetStringAsync(new Uri(address));
reply = DeserializeSsoReply(res);
}
return reply.Succeeded;
}
}
I couldn't find what would be best practices for our scenario so I wonder if there are any possible pitfalls we can encounter with this approach?
When we had a similar situation what we did was for each valid client they had a certificate they could use to create JWTs for the destination (aka us) service. After validating at their origin, their credentials were repackaged and resigned using the certificate we shared with them. For a limited number of valid callers this helped us keep track of the true origin (based on the signing cert) as well as having a standardized payload for our use.
On receipt we validate that the audience is correct (aka us) and that signer is one of the configured signers.
Everyone keeps their own secrets and anyone you want to believe that you are you, you provide your public key. The owner of the key (aka you) always controls your secret but everyone has to agree on the end to end protocol.
I don't consider this reinventing so much as securing the popular path and limiting entry to a know set of users.

Stripe .net "The signature for the webhook is not present in the Stripe-Signature header."

I am using Stripe.net SDK from NuGet. I always get the
The signature for the webhook is not present in the Stripe-Signature header.
exception from the StripeEventUtility.ConstructEvent method.
[HttpPost]
public void Test([FromBody] JObject incoming)
{
var stripeEvent = StripeEventUtility.ConstructEvent(incoming.ToString(), Request.Headers["Stripe-Signature"], Constants.STRIPE_LISTENER_KEY);
}
The WebHook key is correct, the Request Header contains "Stripe-Signature" keys.
I correctly receive incoming data from the Webhook tester utility (using nGrok with Visual Studio).
the secureCompare method seems to be the culprit => StripeEventUtility.cs
I tried to manipulate the incoming data from Stripe (Jobject, string, serializing...). The payload signature may cause some problem.
Has anybody had the same problem?
As per #Josh's comment, I received this same error
The signature for the webhook is not present in the Stripe-Signature header.
This was because I had incorrectly used the API secret (starting with sk_) to verify the HMAC on EventUtility.ConstructEvent.
Instead, Stripe WebHook payloads are signs with the Web Hook Signing Secret (starting with whsec_) as per the docs
The Web Hook Signing Secret can be obtained from the Developers -> WebHooks page:
The error can also occur because you are using the secret from the Stripe dashboard. You need to use the temporary one generated by the stripe cli if you are using the CLI for testing.
To obtain it run this:
stripe listen --print-secret
Im not sure about reason of this, but Json readed from Request.Body has a little bit different structure than parsed with [FromBody] and Serialized to string.
Also, you need to remove [FromBody] JObject incoming because then Request.Body will be empty.
The solution you need is:
[HttpPost]
public void Test()
{
string bodyStr = "";
using (var rd = new System.IO.StreamReader(Request.Body))
{
bodyStr = await rd.ReadToEndAsync();
}
var stripeEvent = StripeEventUtility.ConstructEvent(bodyStr, Request.Headers["Stripe-Signature"], Constants.STRIPE_LISTENER_KEY);
}
I was also receiving the same exception message when I looked at it in the debugger but when I Console.WriteLine(e.Message); I received a different exception message.
Received event with API version 2020-08-27, but Stripe.net 40.5.0 expects API version 2022-08-01. We recommend that you create a WebhookEndpoint with this API version. Otherwise, you can disable this exception by passing throwOnApiVersionMismatch: false to Stripe.EventUtility.ParseEvent or Stripe.EventUtility.ConstructEvent, but be wary that objects may be incorrectly deserialized.
I guess your best bet is to set throwOnApiVersionMismatch to false;
EventUtility.ParseEvent(json, header, secret, throwOnApiVersionMismatch: false)

Instasharp in a Console Application

I have a .net console application that I want to use to pull Instagram posts via the Instasharp wrapper using a hashtag search.
I use C# .net web forms extensively and am not very familiar with MVC nor how to use the await keyword. The code sample below seems to run, but never provides any output.
This line:
var tagInfo = await tagApi.Get("soccer");
Returns me to the calling method with no indication of retrieved data.
Can anyone provide insights as to what I am doing wrong here?
public static async void GetInstagram(String tag, InstagramConfig config)
{
var instagramPosts = await LoadInstagramPosts(tag, config);
dynamic dyn = JsonConvert.DeserializeObject(instagramPosts.ToString());
foreach (var data in dyn.data)
{
Console.WriteLine("{0} - {1}", data.filter, data.images.standard_resolution.url);
}
}
public static async Task<TagResponse> LoadInstagramPosts(String hashTagTerm, InstagramConfig config)
{
var tagApi = new InstaSharp.Endpoints.Tags(config);
var tagInfo = await tagApi.Get("soccer");
}
EDITED code after first comment which solved my initial problem.
I feel like I'm close but something is still missing.
See specific questions below...
I've based the code on the documentation from InstaSharp GitHub (https://github.com/InstaSharp/InstaSharp). GitHubs example is based on an MVC application, mine is not an MVC project, but a console application.
I feel like I am very close and maybe others will benefit from helping me solve this.
My specific questions...
1) Not sure where the 'code' parameter in the OAuth method originate??
2) How to perform the needed call backs with Instagram??
var config = new InstaSharp.InstagramConfig(location.InstagramClientId, location.InstagramClientSecret, "http://localhost");
string instagramLoginLink = InstagramLogin(config);
GetInstagram("soccer", config, instagramLoginLink);
public static async void GetInstagram(String tag, InstagramConfig config, string code)
{
OAuthResponse oAuthResponse = await OAuth(code, config);
var instagramPosts = await LoadInstagramPosts(tag, config, oAuthResponse);
if(instagramPosts.Data != null)
{
dynamic dyn = JsonConvert.DeserializeObject(instagramPosts.Data.ToString());
foreach (var data in dyn.data)
{
Console.WriteLine("{0} - {1}", data.filter, data.images.standard_resolution.url);
}
}
}
public static string InstagramLogin(InstagramConfig config)
{
var scopes = new List<OAuth.Scope>();
scopes.Add(InstaSharp.OAuth.Scope.Likes);
scopes.Add(InstaSharp.OAuth.Scope.Comments);
string link = InstaSharp.OAuth.AuthLink(config.OAuthUri + "authorize", config.ClientId, config.RedirectUri, scopes, InstaSharp.OAuth.ResponseType.Code);
return link;
}
public static async Task<OAuthResponse> OAuth(string code, InstagramConfig config)
{
// add this code to the auth object
var auth = new OAuth(config);
// now we have to call back to instagram and include the code they gave us
// along with our client secret
return await auth.RequestToken(code);
}
public static async Task<TagResponse> LoadInstagramPosts(String hashTagTerm, InstagramConfig config, OAuthResponse OAuth)
{
var tagApi = new InstaSharp.Endpoints.Tags(config, OAuth);
return await tagApi.Get("soccer");
}
I'm a bit late to the show, yet probably my answer will help someone who find this question when googling, someone like me.
The main problem with your approach is that Instagram is using OAuth authentication. I suggest you to google on OAuth to understand the principles, but I will try to explain the practical points of it below.
OAuth approach means that the result of the InstagramLogin method in the snippet above is not the code. It's the link where you need to send you user (yes, using a browser or a web-view) so that they can sign into their Instagram account and then authorize your application to access their data (so-called user consent screen).
In the end, after user consent, Instagram will redirect browser to the URL of your choice (it should be previously added in the list of allowed redirect urls in Instagram API -> Manage Clients (top-right corner) -> Create/Select client to Manage -> Security tab)
You can try set a breakpoint and copy the value of instagramLoginLink into your browser's address box. You will be able to see the whole flow of authentication and consent - and finally the redirect url that will most probably produce 404 in your browser.
This final link will contain the code in a get parameter. It's the so-called grant code that allows you to get an access token. This code is to be extracted from url and then used in your call to OAuth.RequestToken).
PS: Yes, everything I say above means that you need either a web app running that will redirect user to Instagram or a client-side app that will show the user a web view and somehow handle the moment when Instagram sends the user back to your redirect url - to grab the code and proceed.

Is it possible to secure an ASP.NET Web API 2 application against more than one authentication provider?

I just finished this excellent article about securing an ASP.NET Web API 2 application with OAuth2 against an ADFS/Windows Azure AD instance using ADAL and OWIN middleware components.
However, it seems that the whole authentication workflow described in this article is very much "hard-wired" into the HTTP request pipeline and doesn't leave any room for the implementation of authentication workflows against other authentication providers.
Why is this needed?
I have a mobile web client in which "internal" and "external" users are allowed to authenticate in order to issue requests for user relevant data against an API endpoint.
While the "internal" users are obtaining their authentication tokens from Azure AD/ADFS the "external" users have to authenticate against another system which issues another kind of authentication token.
Therefore I have to be able to distinguish between requests from "internal" and "external" users on the API endpoint level in order to kick off the correct evaluation workflow for their different authentication tokens.
Any indications on how to achieve this would be highly appreciated.
Regards, Matthias
After a little bit of digging I found the following answer which describes how to programmatically validate an JWT based authentication token issued by an ADFS OAuth 2.0 authentication flow using the JwtSecurityTokenHandler class. Code examples can be found in the linked answer.
This would allow me to create a custom authorization filter which I can then use as an attribute on controllers or controller methods. This filter would analyze the Authorization header in the client request, detect the type of authentication token contained in it and then kick off the respective program logic to validate/verify the authentication token.
Something along these lines maybe:
public enum AuthTokenType
{
OAuth2Bearer,
Custom
}
public class CustomAuthenticationAttribute : IAuthenticationFilter
{
public bool AllowMultiple
{
get
{
throw new NotImplementedException();
}
}
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage incommingRequest = context.Request;
HttpHeaders headers = incommingRequest.Headers;
string authHeader = GetHeader(headers, "Authorization");
AuthTokenType authTokenType = DetecteAuthTokenType(authHeader);
if (authTokenType == AuthTokenType.OAuth2Bearer)
{
// Validate auth token using the JwtSecurityTokenHandler class
}
else if (authTokenType == AuthTokenType.Custom)
{
// Validate auth token using whatever is necessary
}
else
{
// auth token doesn't correspond to a recognized type or hasn't been part of the client request - reject request
}
}
public AuthTokenType DetectAuthTokenType(string authHeader)
{
// Analyze the authorization header string and return its proper type
}
private string GetHeader(HttpHeaders headers, string key)
{
IEnumerable<string> keys = null;
if (!headers.TryGetValues(key, out keys))
return null;
return keys.First();
}
}

How to set identity/username in ServiceAuthorizationManager?

I have a WCF 4.0 REST service on server side (hosted in IIS) and an Android client. The Android client sends an encrypted security token in a custom HTTP header in order to authenticate the user. I have implemented a custom ServiceAuthorizationManager which extracts the security token from the header. The token contains the username which I can read from the token:
public class MyAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
var requestMessage = operationContext.RequestContext.RequestMessage;
var requestProperty = (HttpRequestMessageProperty)requestMessage
.Properties[HttpRequestMessageProperty.Name];
var token = requestProperty.Headers["X-MyCustomHeader"];
if (!string.IsNullOrEmpty(token))
{
var userName = GetUserNameFromToken(token);
if (!string.IsNullOrEmpty(userName))
{
// How to save userName now so that I can
// retrieve it in the service operations?
return true;
}
}
return false;
}
}
Now, my problem is that I also need the name of the authenticated user in various service operations (mainly to access user profile data) and I was planning to retrieve it this way:
public void MyServiceOperation()
{
string userName = OperationContext.Current
.ServiceSecurityContext.PrimaryIdentity.Name;
// check profile store for that userName and do something depending on
// profile settings
}
How can I set this username in CheckAccessCore?
A very naive trial like this...
operationContext.ServiceSecurityContext.PrimaryIdentity.Name = userName;
...doesn't work because PrimaryIdentity.Name is readonly. I assume that more sophisticated code is required.
After some research I didn't find a way to set the identity in ServiceAuthorizationManager.CheckAccessCore. This method seems to be called too late in the processing pipeline when the user's identity is already set (possibly to "Anonymous" (IsAuthenticated is false)) and cannot be changed anymore. The ServiceAuthorizationManager is made for authorization, not authentication, therefore it's the wrong place to implement a custom authentication.
I've finally found three possible ways for my problem:
As explained in the article linked in #TheCodeKing's answer, using the WCF REST Starter Kit provides the option to write a custom RequestInterceptor which hooks in early enough into the pipeline, allows access to the incoming request and allows to set the user's identity based on, for example, custom HTTP headers. Unfortunately the WCF REST Starter Kit is old (based on WCF 3.5) and the development has apparently been abandoned. Some of its features have been incorporated into WCF 4.0, but some have not, and the RequestInterceptor is among them. Nonetheless I've used this solution now and mixed the Microsoft.ServiceModel.Web assembly from the Starter Kit into my WCF 4.0 solution. It seems to work so far after a few simple tests.
If the identity is not really necessary but only the user name, a simple "trick"/"hack" to write the user name into a new request header works (also in CheckAccessCore):
// ...
var userName = GetUserNameFromToken(token);
if (!string.IsNullOrEmpty(userName))
{
requestProperty.Headers["X-UserName"] = userName;
return true;
}
// ...
And then in the service methods:
public void MyServiceOperation()
{
string userName = WebOperationContext.Current.IncomingRequest
.Headers["X-UserName"];
// ...
}
Another and more low level option would be to write a custom HttpModule which intercepts the incoming request and sets the identity. An example how this could look like is here from Microsoft Pattern & Practices team (see the "HTTP Module Code" example in the mid of the article).
Take a look at this article. It has examples of setting up the IAuthorizationPolicy instance. By creating your own implementation you can control the creation of the IPrincipal and IIdentity instances which are passed around in the context. It's all hooked in from a service interceptor.
internal class AuthorizationPolicyFactory
{
public virtual IAuthorizationPolicy Create(Credentials credentials)
{
var genericIdentity = new GenericIdentity(credentials.UserName);
var genericPrincipal = new GenericPrincipal(genericIdentity,
new string[] { });
return new PrincipalAuthorizationPolicy(genericPrincipal);
}
}

Categories

Resources