Adding security to RESTful API [closed] - asp.net

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 6 years ago.
Improve this question
I am wanting to implement two websites that need to communicate with each other. (Since one of the sites has a separate deployment for each customer, and is spread across many servers, sharing a database or communicating privately is not an option.) So I've been looking into RESTful APIs.
Unfortunately, I'm running into a lot of information that I'm not familiar with. One issue is security. We don't need anything fancy--we're not a bank or anything. I think we can just get away with HTTPS and a basic username and password.
Questions:
How would I pass the username and password to the API? Would they just be passed as bare arguments in the URL?
Does .NET provide any mechanism for authorizing such username and passwords, or do I just manually see if the password is in our database on each and every request? (I would hash for security.)

How would I pass the username and password to the API? Would they just
be passed as bare arguments in the URL?
It can be either in the URL or in the header. If you are using HTTPS, it will all be encrypted so it will not be bare. Please see this for more details.
Does .NET provide any mechanism for authorizing such username and
passwords, or do I just manually see if the password is in our
database on each and every request? (I would hash for security.)
No you do not need to check the database on every request. You can check once, create a token with an expiry and the client can keep sending you the token. This way you do not have to keep checking the database every single time.
Please see see this answer for some helpful information.
I think basic authentication with base64 encoding will be sufficient. If not you can always change it. Here are the different ways to apply it to your backend code:
To apply an authentication filter to a controller, decorate the controller class with the filter attribute. The following code sets the [IdentityBasicAuthentication] filter on a controller class, which enables Basic Authentication for all of the controller's actions.
[IdentityBasicAuthentication] // Enable Basic authentication for this controller.
[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
public IHttpActionResult Get() { . . . }
public IHttpActionResult Post() { . . . }
}
To apply the filter to one action, decorate the action with the filter. The following code sets the [IdentityBasicAuthentication] filter on the controller's Post method.
[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
public IHttpActionResult Get() { . . . }
[IdentityBasicAuthentication] // Enable Basic authentication for this action.
public IHttpActionResult Post() { . . . }
}
To apply the filter to all Web API controllers, add it to GlobalConfiguration.Filters.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new IdentityBasicAuthenticationAttribute());
// Other configuration code not shown...
}
}
Finally here is an example of the implementation, you may change it as you need:
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using BasicAuthentication.Results;
namespace BasicAuthentication.Filters
{
public abstract class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter
{
public string Realm { get; set; }
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
if (authorization == null)
{
// No authentication was attempted (for this authentication method).
// Do not set either Principal (which would indicate success) or ErrorResult (indicating an error).
return;
}
if (authorization.Scheme != "Basic")
{
// No authentication was attempted (for this authentication method).
// Do not set either Principal (which would indicate success) or ErrorResult (indicating an error).
return;
}
if (String.IsNullOrEmpty(authorization.Parameter))
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
return;
}
Tuple<string, string> userNameAndPasword = ExtractUserNameAndPassword(authorization.Parameter);
if (userNameAndPasword == null)
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Invalid credentials", request);
return;
}
string userName = userNameAndPasword.Item1;
string password = userNameAndPasword.Item2;
IPrincipal principal = await AuthenticateAsync(userName, password, cancellationToken);
if (principal == null)
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
}
else
{
// Authentication was attempted and succeeded. Set Principal to the authenticated user.
context.Principal = principal;
}
}
protected abstract Task<IPrincipal> AuthenticateAsync(string userName, string password,
CancellationToken cancellationToken);
private static Tuple<string, string> ExtractUserNameAndPassword(string authorizationParameter)
{
byte[] credentialBytes;
try
{
credentialBytes = Convert.FromBase64String(authorizationParameter);
}
catch (FormatException)
{
return null;
}
// The currently approved HTTP 1.1 specification says characters here are ISO-8859-1.
// However, the current draft updated specification for HTTP 1.1 indicates this encoding is infrequently
// used in practice and defines behavior only for ASCII.
Encoding encoding = Encoding.ASCII;
// Make a writable copy of the encoding to enable setting a decoder fallback.
encoding = (Encoding)encoding.Clone();
// Fail on invalid bytes rather than silently replacing and continuing.
encoding.DecoderFallback = DecoderFallback.ExceptionFallback;
string decodedCredentials;
try
{
decodedCredentials = encoding.GetString(credentialBytes);
}
catch (DecoderFallbackException)
{
return null;
}
if (String.IsNullOrEmpty(decodedCredentials))
{
return null;
}
int colonIndex = decodedCredentials.IndexOf(':');
if (colonIndex == -1)
{
return null;
}
string userName = decodedCredentials.Substring(0, colonIndex);
string password = decodedCredentials.Substring(colonIndex + 1);
return new Tuple<string, string>(userName, password);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
Challenge(context);
return Task.FromResult(0);
}
private void Challenge(HttpAuthenticationChallengeContext context)
{
string parameter;
if (String.IsNullOrEmpty(Realm))
{
parameter = null;
}
else
{
// A correct implementation should verify that Realm does not contain a quote character unless properly
// escaped (precededed by a backslash that is not itself escaped).
parameter = "realm=\"" + Realm + "\"";
}
context.ChallengeWith("Basic", parameter);
}
public virtual bool AllowMultiple
{
get { return false; }
}
}
}
If you still want to read more then here is a great article which goes into details. I have copied the above code from this article. It has lots of great information.

If you control or exert significant influence on both sides of the connection, client ssl certificates is a really strong and powerful way of doing this. It's attractive to me in this case because it only requires distributing a trusted CA certificate which can be done before the client certificates are created. It's far more secure than any username and password could ever be ( because the password doesn't need to go across the wire).
Any other solution with authentication I can think of, you're going to have to have some sort of data source to verify the credentials. But x509 solves this problem for you. We've done it at work between applications and other than managing the certificates it works really, really well. And it's basically the most secure thing available.
I don't know much about .net in general, but ( not to lmgtfy ) https://support.microsoft.com/en-us/kb/315588 seems like the step by step format you are looking for.

Just a thought, and it really depends on what you meant by "username/password". If this means "authorization"/access to some API call and you want to ensure that the client is "authorized" to make a call to your API (only apps A, B can make api calls to API - and it seems this is what you're looking for based on your comment above):
As in the comment above, authorization header, using JWT. There is an great/easy JWT library in Nuget
it's pretty much something like a "shared secret" used to sign a "payload" (the JWT)
the "sender" will build the JWT and sign it (and add to header or whatever protocol you want - it can be body if prefer it over headers)
the "receiver" will verify the JWT sent
this includes handling/mitigating "replays" - the JWT spec has an "expire" field (exp) that you can have the library validate as well (or not, it's up to you)
The project site is on Github with samples.
Hth.

Related

How do I create a ClaimsPrincipal in my Blazor/.NetCore "Session"?

Background: I have an old MVC app that I'm experimenting with migrating to a shiny new Blazor app. Blazor seems to tick a lot of boxes for me here. Wunderbar. For clarity this is the solution template in VS2022 where there's a WASM, a .Net Core host, and a shared project. I will have plenty of api calls that need to be secured as well as UI that will be affected by various authorization policies (eg show/hide admin features).
I have a table of users with an ID and hashed password.
I can't get Blazor to use its native authentication/authorization processes with my existing store.
My latest attempt was to create an AccountController on the server app (inherits ControllerBase) and put in a Login method that gets the username and password from a json body for the moment. I have successfully ported the old authentication mechanism and I have my user that I have verified the password for. I now want to use Claims and a ClaimsPrincipal to store some of the things about the user, nothing too complex.
How do I put my ClaimsPrincipal into the app such that the WASM UI can see it AND future calls to api controllers (or ControllerBase controllers) will see it?
I have found hundreds of examples that use built-in scaffolding that lets it use EF to create tables and things but I need to use my existing stores and I can't find anything that joins the dots on how to connect the WASM and the server side.
I have read about and implemented and around the place, and tried some #authorize around the place but my WASM just doesn't know about the authenticated user.
In my login controller I have attempted a bunch of different approaches:
I implemented a custom AuthenticationStateProvider, got it into the controller via DI, called the AuthenticationStateChanged() and for the lifecycle of that one controller call I can see my HttpContext.User gets the new identity. But the WASM doesn't, and if I hit the same method again the User is null again
I tried to implement a SignInManager. This never worked well and my reading suggests that it's not compatible
I discovered ControllerBase.SignIn() which hasn't helped either
HttpContext.SignInAsync() with Cookie authentication (because that was the example I found)
I tried setting HttpContext.User directly (and tried combining that one call with the AuthenticationStateProvider implementation simultaneously)
I tried creating a fresh solution from template to pick through it, but it would appear to be reliant on hacking up my EF DataContext. I just want to find how I tell the whole contraption "Here's a ClaimsPrincipal" and have that work in both the WASM and api controllers.
I'm also not excited to have a dependency on the Duende stuff - I don't see what it brings to the table. I don't really need a whole identity provider, I already have my own code for authorizing against the database I just need to get my very boring ClaimsPrincipal into my app.
Am I going at this all wrong? Has my many years of "old school" experience stopped me from seeing a modern way of doing this? Am I trying to force cool new stuff to behave like clunky old stuff? Yes I'd love to switch to Google/Facebook/Twitter/MS authorization but that's not an option, I have passwords in a database.
You need to build a custom AuthenticationHandler.
Here's the relevant bits of one of mine (see credits at bottom for where I lifted some of the code). You'll need to pick out the pieces from the code to make your work. Ask if you have any specific problems.
The custom AuthenticationHandler looks up your user in your database and if authenticated, builds a standard ClaimsPrincipal object and adds it to the security header. You can then use the standard Authorization and AuthenticationStateProvider.
public class AppAuthenticationHandler : AuthenticationHandler<AppAuthOptions>
{
private const string AuthorizationHeaderName = "Authorization";
private const string BasicSchemeName = "BlazrAuth";
//this is my custom identity database
private IIdentityService _identityService;
public AppAuthenticationHandler(IOptionsMonitor<AppAuthOptions> options, IIdentityService identityService, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
_identityService = identityService;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
await Task.Yield();
// Check the Headers and make sure we have a valid set
if (!Request.Headers.ContainsKey(AuthorizationHeaderName))
return AuthenticateResult.Fail("No Authorization Header detected");
if (!AuthenticationHeaderValue.TryParse(Request.Headers[AuthorizationHeaderName], out AuthenticationHeaderValue? headerValue))
return AuthenticateResult.Fail("No Authorization Header detected");
if (!BasicSchemeName.Equals(headerValue.Scheme, StringComparison.OrdinalIgnoreCase))
return AuthenticateResult.Fail("No Authorization Header detected");
if (headerValue is null || headerValue.Parameter is null)
return AuthenticateResult.Fail("No Token detected");
// Get the User Guid from the security token
var headerValueBytes = Convert.FromBase64String(headerValue.Parameter);
var userpasswordstring = Encoding.UTF8.GetString(headerValueBytes);
// This will give you a string like this "me#you.com:password"
if (youcantdecodethestring ))
return AuthenticateResult.Fail("Invalid Token submitted");
// Get the user data from your database
var principal = await this.GetUserAsync(userId);
if (principal is null)
return AuthenticateResult.Fail("User does not Exist");
// Create and return an AuthenticationTicket
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
// method to get the user from the database and retuen a ClaimsPrincipal
public async Task<ClaimsPrincipal?> GetUserAsync(Guid Id)
{
// Get the user object from the database
var result = await _identityService.GetIdentityAsync(Id);
// Construct a ClaimsPrincipal object if the have a valid user
if (result.Success && result.Identity is not null)
return new ClaimsPrincipal(result.Identity);
// No user so return null
return null;
}
}
You can construct a ClaimsIdentity like this:
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Sid, record.Id.ToString()),
new Claim(ClaimTypes.Name, record.Name),
new Claim(ClaimTypes.Role, record.Role)
}, "MyIdentityProvider");
public class AppAuthOptions : AuthenticationSchemeOptions
{
public string Realm = "BlazrAuth";
}
The service registration:
public static class AuthServicesCollection
{
public static void AddAppAuthServerServices(this IServiceCollection services)
{
services.AddAuthentication("BlazrAuth").AddScheme<AppAuthOptions, AppAuthenticationHandler>("BlazrAuth", null);
services.AddScoped<IIdentityService, IdentityService>();
}
}
Credits: Some of this code was derived from: https://harrison-technology.net/

Best practice for deploying spring boot application on Amazon

I've devloped a chat bot application using the Facebook Messenger platform.
I used Spring Boot with embedded Tomcat for the web platform.
The application should run on Amazon aws, open to the WWW, and to be used as a webhook for recieving callbacks from Messenger over https.
I need an advice how to secure the application, so it won't be hacked or flooded with requests that are not coming from Facebook.
I thought to make the application require secured (ssl) connection, but using the "security.require_ssl=true" in application.properties didn't do the work. Perhaps I don't know what is the meaning of this and how to configure it propertly.
Is there a best practice how to block requests which are not https requests? Or a way to block requests which are coming outside Messenger in the application level?
Thank you very much!
EDIT
In the meantime, I blocked requests from other IPs in application layer using the handler interceptor:
#Configuration
public class MyWebApplicationInitializer implements WebApplicationInitializer, WebMvcConfigurer{
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (! (request.getRemoteAddr().equals("173.252.88.66") || request.getRemoteAddr().equals("127.0.0.1")|| request.getRemoteAddr().equals("0:0:0:0:0:0:0:1"))){
logger.warn("Request is not coming from authorized remote address: " + request.getRemoteAddr()+". Rejecting");
response.getWriter().write("Unauthorized Address");
response.setStatus(401);
return false;
} else {
return true;
}
}
}
You should check the X-Hub-signature HTTP header available in the requests sent by Facebook to your webhook URL.
In your case, you may define a filter or interceptor for the verification of the signature. You can also do it in your controller as in the this example I found in RealTimeUpdateController.java from the spring social project.
private boolean verifySignature(String payload, String signature) throws Exception {
if (!signature.startsWith("sha1=")) {
return false;
}
String expected = signature.substring(5);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
SecretKeySpec signingKey = new SecretKeySpec(applicationSecret.getBytes(), HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(payload.getBytes());
String actual = new String(Hex.encode(rawHmac));
return expected.equals(actual);
}
a lot to say so I am sure I will miss some points.
setting SSL is a first good thing but make sure you get a certificate. lets encrypt is a good thing if you dont want to pay for SSL certificate.
Just seeing aws provides an alternative to letsencrypt
Security Group You can see Security Group as something similar to a firewall so you can control which port is opened, external and internal flows.
Look at IAM which control who and how can get access to your AWS account
obvious : change your password. do not let default password for installation you could make on the instance
read some of https://aws.amazon.com/security/security-resources/ to get more information about what you can do
it won't be hacked or flooded with requests
sorry to say but most probably it will be - It does not need to be an advanced hacker to run scanner and scan IPs and check open ports / brute force login etc ...
Thanks to Guy Bouallet help I added the signature check.
I added it in my controller and not in the interceptor, to avoid the problem of How to read data twice in spring which seems a little complicated.
So here is it:
#RequestMapping(path = "/")
public void doHandleCallback(#RequestBody String body, #RequestHeader(value = "X-Hub-Signature") String signature) throws IOException {
if (!verifyRequestSignature(body.getBytes(), signature)){
logger.error ("Signature mismatch.");
throw new MismatchSignatureException(signature);
}
MessengerCallback callback = mapper.readValue(body, MessengerCallback.class);
logger.info("Incoming Callback: " + body );
for (EventData entry : callback.getEntry()) {
for (ReceivedMessagingObject message : entry.getMessaging()) {
if (message.isMessage() || message.isPostback()) {
doHandleMessage(message);
}
else if (message.isDelivery()){
doHandleDelivery(message);
}
}
}
}
private boolean verifyRequestSignature(byte[] payload, String signature) {
if (!signature.startsWith("sha1="))
return false;
String expected = signature.substring(5);
System.out.println("Expected signature: " + expected); //for debugging purposes
String hashResult = HmacUtils.hmacSha1Hex(APP_SECRET.getBytes(), payload);
System.out.println("Calculated signature: " + hashResult);
if (hashResult.equals(expected)) {
return true;
} else {
return false;
}
}
And this is the Exception handling class:
#ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="Request Signature mismatch")
public class MismatchSignatureException extends RuntimeException {
private String signature;
public MismatchSignatureException(String signature) {
this.signature = signature;
}
#Override
public String getMessage() {
return "Signature mismatch: " + signature;
}

Authorization_code grant flow on Owin.Security.OAuth: returns invalid_grant

I am trying to setup my authentication using the authorization_code grant flow. I had it previously working with grant_type=password, so I kind of know how the stuff is supposed to work. But when using grant_type=authorization_code, I couldn't make it return anything other than invalid_grant
Here is my setup:
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/auth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
Provider = new SampleAuthProvider()
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
AuthenticationType = "Bearer"
});
SampleAuthProvider is the following class: https://gist.github.com/anonymous/8a0079b705423b406c00
Basically, it's just logging every step and validating it. I tried the request:
POST http://localhost:12345/auth/token
grant_type=authorization_code&code=xxxxxx&client_id=xxxxx&redirect_uri=https://xxxx.com/
Content-Type: application/x-www-form-urlencoded
It's going through:
OnMatchEndpoint
OnValidateClientAuthentication
And that's all. I expected it to call OnValidateTokenRequest and OnGrantAuthorizationCodenext, but it just didn't. I have no idea why.
The xxxx's in the request aren't placeholders, I tried it like that. Maybe the middleware makes some checks on its own and rejects the request because of that? I tried variants of the redirect_uri with http, without any protocol, without trailing slash...
It also works properly with a custom grant_type. It so if I too desperate, I guess I can use that to simulate authorization_code, but I'd rather not have to do that.
TL;DR
My OAuthAuthorizationServerProvider returns {"error":"invalid_grant"}after OnValidateClientAuthentication when using grant_type=authorization_code.
Why is it stopping there?
How can I make the whole damn thing work?
Thanks for your help!
Edit
As pointed out by RajeshKannan, I made a mistake in my configuration. I didn't provide an AuthorizationCodeProvider instance. However, that didn't completely solve the problem, since in my case, the code is not issued by the AuthorizationCodeProvider, and I can't just deserialize it. I anwered with the workaround I got working.
Here is what I got working. I'm not completely comfortable with that solution, but it works and should help others to fix their issues.
So, the issue is that I didn't set the AuthorizationCodeProvider property. When a request with grant_type=authorization_code is received, the code must be validated by that code provider. The framework assumes that the code was issued by that code provider, but that's not my case. I get it from another server and have to send the code back to it for validation.
In the standard case, where you are also the one issuing the code, the link provided by RajeshKannan describes everything you have to do.
Here is where you have to set the property:
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString(Paths.TokenPath),
Provider = new SampleAuthProvider(),
AuthorizationCodeProvider = new MyAuthorizationCodeProvider ()
}
And the declaration of the MyAuthorizationCodeProvider class:
internal class MyAuthorizationCodeProvider : AuthenticationTokenProvider
{
public override async Task ReceiveAsync(
AuthenticationTokenReceiveContext context)
{
object form;
// Definitely doesn't feel right
context.OwinContext.Environment.TryGetValue(
"Microsoft.Owin.Form#collection", out form);
var redirectUris = (form as FormCollection).GetValues("redirect_uri");
var clientIds = (form as FormCollection).GetValues("client_id");
if (redirectUris != null && clientIds != null)
{
// Queries the external server to validate the token
string username = await MySsoService.GetUserName(context.Token,
redirectUris[0]);
if (!string.IsNullOrEmpty(username))
{
var identity = new ClaimsIdentity(new List<Claim>()
{
// I need the username in GrantAuthorizationCode
new Claim(ClaimTypes.NameIdentifier, username)
}, DefaultAuthenticationTypes.ExternalBearer);
var authProps = new AuthenticationProperties();
// Required. The request is rejected if it's not provided
authProps.Dictionary.Add("client_id", clientIds[0]);
// Required, must be in the future
authProps.ExpiresUtc = DateTimeOffset.Now.AddMinutes(1);
var ticket = new AuthenticationTicket(identity, authProps);
context.SetTicket(ticket);
}
}
}
}
I had the same error. Things I was missing:
Specify OAuthAuthorizationServerOptions.AuthorizationCodeProvider according to the documentation.
Specify the same client_id as a GET-parameter when making a request to the token endpoint as you did when you received the authorization_code.
Override OAuthAuthorizationServerProvider.ValidateClientAuthentication and in this method call context.TryGetFormCredentials. This sets the property context.ClientId to the value from the client_id GET-parameter. This property must be set, otherwise you'll get the invalid_grant error. Also, call context.Validated().
After doing all of the above, I could finally exchange the authorization_code to an access_token at the token endpoint.
Thanks scenario, My code was missing the following two required values. Posted here in case others find it useful:
// Required. The request is rejected if it's not provided
authProps.Dictionary.Add("client_id", clientIds[0]);
// Required, must be in the future
authProps.ExpiresUtc = DateTimeOffset.Now.AddMinutes(1);
Make sure that you have configured your authorization server options.
I think you should provide your authorize end point details:
AuthorizeEndpointPath = new PathString(Paths.AuthorizePath)
In the below link, the authorization code grant will be explained in detail and it lists the method which were involved in authorization code grant life cycle.
Owin Oauth authorization server
The answer by #dgn more or less worked for me. This is just an extension to that. As it turns out, you can supply whatever string you want to the ClaimsIdentity constructor. The following works just as well, and doubles up as a detailed code comment:
var identity = new ClaimsIdentity(
#"Katana - What a shitty framework/implementation.
Unintuitive models and pipeline, pretty much have to do everything, and the docs explain nothing.
Like what can go in here? WTF knows but turns out as long as _something_ is in here,
there is a client_id key in your AuthenticationProperties with the same value as
what's set inside your implementation for OAuthAuthorizationServerProvider.ValidateClientAuthentication, and
your AuthenticationProperties.ExpiresUtc is set to some time in the future, it works.
Oh and you don't actually need to supply an implementation for OAuthAuthorizationServerProvider.GrantAuthorizationCode...
but if you are using the resource owner grant type, you _do_ need to supply an implementation of
OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials. Hmm. Whatever.
Katana and IdenetityServer - two frameworks that are absolute garbage. In the amount of time it took me to
figure out all the observations in this paragraph, I could've written my own /token endpoint."
);
I solved this with the the following simplest example and would like to share it. Hope someone find it helpful.
--
It seems the middleware will check if the key redirect_uri exists in the dictionary of AuthenticationProperties, remove it and everything works fine(with validated context).
A simplified example of AuthorizationCodeProvider woubld be like so:
public class AuthorizationCodeProvider:AuthenticationTokenProvider {
public override void Create(AuthenticationTokenCreateContext context) {
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context) {
context.DeserializeTicket(context.Token);
context.Ticket.Properties.Dictionary.Remove("redirect_uri"); // <-
}
}
And don't forget to make the context validated in the overridden method OAuthAuthorizationServerProvider.ValidateClientAuthentication. Again, here's a simplified example which inherit from the ApplicationOAuthProvider class of the template project:
public partial class DefaultOAuthProvider:ApplicationOAuthProvider {
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) {
if(null!=context.RedirectUri) {
context.Validated(context.RedirectUri);
return Task.CompletedTask;
}
return base.ValidateClientRedirectUri(context);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) {
if(context.TryGetFormCredentials(out String clientId, out String clientSecret)) {
// Specify the actual expected client id and secret in your case
if(("expected-clientId"==clientId)&&("expected-clientSecret"==clientSecret)) {
context.Validated(); // <-
return Task.CompletedTask;
}
}
return base.ValidateClientAuthentication(context);
}
public DefaultOAuthProvider(String publicClientId) : base(publicClientId) {
}
}
Note that if you invoke context.Validated with a particular client id, then you will have to put the same client_id in the properties of the ticket, you can do that with the method AuthenticationTokenProvider.Receive

ASP.NET Web API how to authenticate user

I'm trying to create a simple user authentication function but I just can't get it to work.
Here is the code I'm working on:
public class LoginController : ApiController
{
private void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
public bool Login(string token)
{
//Check token
if (.....)
{
//Authenticate user
var identity = new GenericIdentity("Test user");
SetPrincipal(new GenericPrincipal(identity, new string[]{"Test role"}));
}
}
[Authorize]
public string TestFun()
{
return "Hello " + User.Identity.Name;
}
}
So, if I try to call method TestFun() first, it returns error code 401 like it should.
However when I call method Login() it should somehow save user credentials, but this is where I get lost, I just can't get it to work.
TestFun() always returns error code 401 even if I call Login() first.
If I try to put return "Hello " + User.Identity.Name; in the Login() function it returns correct username, but in the TestFun() the user is not available.
I've even tried using Sessions and FormsAuthentication but I just can't get it to work, even on this really simple example.
Can someone please tell me what am I missing?
Thanks!
The Login method sets the principal for current request only. Just after the request completes, the principal context is wiped out so that the server can handle other requests for other users. When a new request comes, eons later from the server perspective, the principal context no longer exists and if nothing restores it, the request is unauthenticated.
To fix this you have to return something from your login method to the client. Not only bool but rather - an authentication token. Something the client could use to authenticate further requests.
It could be anything. Forms cookie would be fine as long as the client remembers to append it to further requests. Another common practice is to have a custom authentication token returned to the client and then appended by the client in a custom authentication header. And as forms cookies are handled by the Forms Authentication module, custom headers would need a custom mvc authentication filter or custom asp.net authentication module so that the token is readed, the identity is extracted and restored just before the request is about to execute.
If you don't like to bake your own token infrastructure, I would also recommend OAuth2 tokens. There is a great book that contains easy to follow examples on this and other possible authentication methods:
http://www.amazon.com/Pro-ASP-NET-Web-API-Security/dp/1430257822/ref=sr_1_1?ie=UTF8&sr=8-1&keywords=web+api+security
I just got the same issue, yes, I agreed we need to save that principal into somewhere (cookie, session) for other action to use, so, in SetPrincipal function I added
HttpContext.Current.Session["user"] = HttpContext.Current.User;
Now, the issue is how to get it back for other action, the idea popups in my mind is to extend AuthorizeAttribute and override IsAuthrized function, it will read the session first and if it found the session, it will return true, otherwise it will return false.
namespace BinZ
{
public class MyAuthorizeAttribute:AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext) {
HttpContext.Current.User = HttpContext.Current.Session["user"] as IPrincipal;
return HttpContext.Current.User != null;
}
}
}
Please remember to replace [Authorize] to [MyAuthorizeAttribute] in WebApi controller.
It works for me very well.
Cheers

Passing the username/password from client to web API using GET

for example I have a web API : http://example.com/api/product.
I have a C# client to consume this web API. Something like that to get whole list of product.
// List all products.
HttpResponseMessage response = client.GetAsync("api/products").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
var products = response.Content.ReadAsAsync<IEnumerable<Product>>().Result;
foreach (var p in products)
{
Console.WriteLine("{0}\t{1};\t{2}", p.Name, p.Price, p.Category);
}
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
How do I pass the username and password from C# client to server's API? What I want is when the C# client to get whole product list from web API.
The client will send the username and password to the server's API. if the server's web API checks whether it is authorized user from database, if not don't let it get product list.
I used the following approach in a proof of concept some time ago, I hope it helps you.
I wrote something like this, an "AuthenticationController" with 2 methods:
public bool Login(string username, string password, bool rememberMe)
{
if (Membership.ValidateUser(username, password))
{
FormsAuthentication.SetAuthCookie(username, rememberMe);
return true;
}
return false;
}
public void Logout()
{
FormsAuthentication.SignOut();
}
The Login method creates a cookie that will be sent to the client; then, in each request, you need to send it back to the server. You can use the [Authorize] attribute in your controller actions to validate allowed roles and rights.
My recommendation is to use have an authentication routine that will assign a token to the client. The client would then cache that token and pass that token in subsequent requests. The authentication routine should be via SSL to prevent sniffing on the wire and shouldn't be stored on the device at all (the token can be cached to the device).
This will give you a fair bit of control over the client. Your service is then in a position where it can preemptively deactivate the client (kill the token and force a re-auth - essentially a timemout situation). You are also in a position to protect your application on the client (if the application is compromised on the device the user credentials won't be passed around).
You could use DotNetOpenAuth to get you started along this path.
[System.Web.Mvc.AcceptVerbs(HttpVerbs.Post)]
public ActionResult LogOn(string loginIdentifier)
{
if (!Identifier.IsValid(loginIdentifier))
{
ModelState.AddModelError("loginIdentifier",
"The specified login identifier is invalid");
return View();
}
else
{
var openid = new OpenIdRelyingParty();
IAuthenticationRequest request = openid.CreateRequest(
Identifier.Parse(loginIdentifier));
// Require some additional data
request.AddExtension(new ClaimsRequest
{
BirthDate = DemandLevel.NoRequest,
Email = DemandLevel.Require,
FullName = DemandLevel.Require
});
return request.RedirectingResponse.AsActionResult();
}
}
Source: Sample Code

Resources