Secure Web API Post Method with Username and Password - asp.net-web-api

I have a Web API service hosted in Microsoft Azure. I need a certain POST method to be only accessible with one unique username and password.
I understand the [Authorize] method does a token based authentication but its not tied to a single username and password. In my app, the web api also does the login authentication, so anyone who registers can access this post method if im not mistaken. (Please correct me if im wrong)
I am new to this could you guide me the right way please.
This is my WebAPI Post method i want to secure access to with specific unique username&pass:
[AllowAnonymous]
[HttpPost, Route("send")]
public async Task<NotificationOutcome> Post([FromBody]string message)
{
string hubName = "myHub";
string hubNameDefaultShared = "myHubNameDefaultShared";
NotificationHubClient hub = NotificationHubClient
.CreateClientFromConnectionString(hubNameDefaultShared, hubName, enableTestSend: true);
string installationId = string.Empty;
var templateParams = new Dictionary<string, string>
{
["messageParam"] = message
};
NotificationOutcome result = null;
if (string.IsNullOrWhiteSpace(installationId))
{
result = await hub.SendTemplateNotificationAsync(templateParams).ConfigureAwait(false);
}
else
{
result = await hub.SendTemplateNotificationAsync(templateParams, "$InstallationId:{" + installationId + "}").ConfigureAwait(false);
}
return result;
}
And this is how I currently access the POST Method:
var client = new RestClient("myWebApiRouteName");
var request = new RestRequest(Method.POST);
request.AddHeader("Postman-Token", "46c23eba-8ca6-4ede-b4fe-161473dc063a");
request.AddHeader("cache-control", "no-cache");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("undefined", messageBody, ParameterType.RequestBody);
try
{
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

Related

Is possible to protect scope (web api) and authenticate client (web app mvc) in same project?

Good morning,
I need to have in same project both web api and web app mvc.
Web api has to be protected via bearer token and web app mvc has to be authenticated via identity server.
Is it possible protecting a scope and a client in same project?
I think I have to do something like this in startup
//this to protect scope api1
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:5000/";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
});
//this to authenticate mvc client
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.AccessDeniedPath = "/account/denied";
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000",
options.RequireHttpsMetadata = false;
options.ResponseType = "id_token token";
options.ClientId = "mvc-implicit";
options.SaveTokens = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("api1");
options.GetClaimsFromUserInfoEndpoint = true;
options.ClaimActions.MapJsonKey("role", "role", "role");
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
Now, I have to call my Api1 using client_credential with an external client.
But it returns me at login page.
Is it possible to do what I want?
Protected WebApi and Authenticated MVC client in same project?
Now, I have to call my Api1 using client_credential with an external client. But it returns me at login page.
That seems you misunderstand the scenario . Your MVC application is client also is a resource application which protected by Identity Server (in Config.cs):
public static IEnumerable<ApiResource> GetApis()
{
return new List<ApiResource>
{
new ApiResource("api1", "My API")
};
}
I assume you have api controller in your MVC application :
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET: api/Values
[HttpGet]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
And you have config to protect the api actions by using AddJwtBearer :
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:5000/";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
});
That means any request to access the Get action should have an authentication bearer header with access token append , the access token is issued by your Identity Server(endpoint is http://localhost:5000/) and the audience is api1 .
Now your another client could use client credential flow to acquire access token to access your web application :
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
// request token
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "client",
ClientSecret = "secret",
Scope = "api1"
});
And call your protected actions :
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
var response = await apiClient.GetAsync("http://localhost:64146/api/values");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
So it won't redirect to login page , since client credential in fact is sending HTTP POST request to get access token with app's credential . There is no login page in this scenario .

Handling AuthenticationFailureResult on client side

I am using pretty much this approach to secure our api with tokens. Not sure, how to handle token expiry response on client side. I have seen two options on api side -
Challenge
WWW-Authenticate header
Let's say my client is requesting for contacts list but their token is expired as it was issued 30 mins ago (our current token age). What do we do now?
Not a good experience if we ask for credentials again
use a do-while loop until we have a successful response. Our client is a plugin so we can store user's credentials in db.
try\catch could be a possibility too
Implementing IdentityServer and refresh tokens would be a overkill for us at this point.
There are several ways you can deal this getting new tokens after they expire. Its not a good experience asking for credentials every time the token expires. Some of the ways to achieve this is to use MemoryCache or text file to store your token then keep reading from it. Once the token expires get a new one and replace the one you stored earlier. Sample code below
public async Task<string> GetToken(HttpClient client)
{
Token token;
MemoryCache memCache = MemoryCache.Default;
var resource = memCache.Get(Settings.TokenKey);
if (resource.IsNotNull())
{
token = (Token)resource;
if (token != null && token.Expires < DateTime.Now)
{
token = await RequestNewToken(client);
return token.AccessToken;
}
}
token = await RequestNewToken(client);
return token.AccessToken;
}
private async Task<Token> RequestNewToken(HttpClient client)
{
var tokenRequestContent = GetTokenRequestContent();
var tokenResponse = client.PostAsync(Settings.TokenUrl, tokenRequestContent).Result;
Token token = null;
MemoryCache memCache = MemoryCache.Default;
if (tokenResponse.IsSuccessStatusCode)
{
token = await tokenResponse.Content.ReadAsAsync<Token>();
memCache.Add(Settings.TokenKey, token, DateTimeOffset.UtcNow.AddHours(2));
}
else
{
var error = await tokenResponse.Content.ReadAsStringAsync();
Console.WriteLine(error);
}
if (token == null) throw new NullReferenceException("Token is null");
return token;
}
private FormUrlEncodedContent GetTokenRequestContent()
{
var credentials = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", Settings.Password),
new KeyValuePair<string, string>("userName", Settings.Username),
new KeyValuePair<string, string>("password", Settings.UserPassword)
};
return new FormUrlEncodedContent(credentials);
}
Settings is just a static class that holds your constants.

ASP.NET Core Secure Api

I have an ASP.NET Core project that has a Web API for mobile device (Xamarin).
I want to secure the api with ASP.NET Core identity, but the problem is when I authenticate a device and authenticated successfully, in another request it not still authenticated:
[HttpPost]
public async Task<IActionResult> Post([FromBody] LogIn l)
{
var user = await userManager.FindByEmailAsync(l.username);
if(user == null)
{
user = await userManager.FindByNameAsync(l.username);
}
if(user != null)
{
await signInManager.SignOutAsync();
Microsoft.AspNetCore.Identity.SignInResult result = await signInManager.PasswordSignInAsync(user, l.password, false, false);
if (result.Succeeded)
{
await signInManager.RememberTwoFactorClientAsync(user);
return Ok("Success");
}
}
return Ok(HttpStatusCode.BadRequest);
}
The code that needs to authorize to return data :
[HttpGet("{id}")]
[Authorize]
public async Task<IActionResult> Get(int id)
{
var b = _context.Books.FirstOrDefault(o => o.BookId == id);
return Ok(b);
}
I read about token and jwt but I don't know how to use them. Any Idea how to secure the API and make the device authenticated once they log in?
I know it's late, but I think the idea is to login the user, and return a token that's then saved to the client's(Xamarin Android/iOS for your case) local storage/Sharedpreferences. The saved token can then be used for subsequent Web API calls for authentication without the need to login. It can then be cleared when a user logs out. For JWT, you can restructure your login function as follows:
var token = await GetJwtSecurityToken(user);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
The GetJwtSecurityToken() can look like this depending on your needs:
private async Task<JwtSecurityToken> GetJwtSecurityToken(ApplicationUser user)
{
var userClaims = await _userManager.GetClaimsAsync(user);
return new JwtSecurityToken(
//issuer: "http://localhost:****/",
//audience: "http://localhost:****/",
audience: "http://localhost:****/",
claims: GetTokenClaims(user).Union(userClaims),//Combine user & claims
//expires: DateTime.UtcNow.AddMinutes(10),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes("x%u<-Q.#w^:qF]2Hz4")), SecurityAlgorithms.HmacSha256)
);
}
The GetTokenClaims() function can look like:
private static IEnumerable<Claim> GetTokenClaims(ApplicationUser user)
{
return new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("UserName", user.UserName),
new Claim("Email", user.Email),
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
new Claim("FirstName", user.FirstName)
//Other user info
};
}
You can then save this token in local storage/Sharedpreferences, and use it to authenticate your API calls. You can research on: How to decode JWT token in Xamarin, OpenId..
Let me know how it goes.

How do I prevent this Google Analytics C# DotNetOpenAuth ProtocolException?

I'm using the Google APIs for .NET. I'm following the example project Simple OAuth2 but I keep getting a Protocol Exception from DotNetOpenAuth.
Here's what I have now:
public static void Main( string[] args )
{
// Register the authenticator.
NativeApplicationClient provider = new NativeApplicationClient(
GoogleAuthenticationServer.Description, gaAppId, gaSecret );
OAuth2Authenticator<NativeApplicationClient> auth = new OAuth2Authenticator<NativeApplicationClient>(
provider, GetAuthorization );
AnalyticsService analyticsService =
new AnalyticsService( new BaseClientService.Initializer {
Authenticator = auth,
ApplicationName = #"Test Application",
} );
DataResource.GaResource.GetRequest request = analyticsService.Data.Ga.Get(
gaId, #"2013-09-04", #"2013-09-18", "ga:totalEvents" );
GaData data = request.Execute();
Console.ReadKey();
}
private static IAuthorizationState GetAuthorization( NativeApplicationClient arg )
{
// Get the auth URL:
IAuthorizationState state =
new AuthorizationState( new[] {AnalyticsService.Scopes.AnalyticsReadonly.GetStringValue()} );
state.Callback = new Uri( NativeApplicationClient.OutOfBandCallbackUrl );
// Retrieve the access token by using the authorization code:
return arg.ProcessUserAuthorization( authCode, state );
}
Notes:
I'm using the Analytics API in this code because that's what I need. I get the same error when using the Tasks API as described in the sample.
The authentication code is a refresher token generated by the process as defined in the example code. The error comes in both cases (request a new token, or re-use an old one.)
The ProtocolException that is triggered by DotNetOpenAuth is there because accounts.google.com returns an error: invalid request.
Here's what the OAuth request looks like:
Aplication/x-www-form-urlencoded; charset=utf-8
User-Agent: DotNetOpenAuth/4.3.1.13153
Host: accounts.google.com
Cache-Control: no-store,no-cache
Pragma: no-cache
Content-Length: 148
Connection: Keep-Alive
code=REDACTED&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&grant_type=authorization_code
And this is what the Google returns:
HTTP/1.1 400 Bad Request
IRRELEVANT_HEADERS
{
"error" : "invalid_request"
}
I'm at a bit of a loss here as to why this happend and how I can solve this. I can't find any other working C# examples. It seems to be a different kind of error than the one in this thread. Is there any of you who knows how I can fix this?
I studied the examples provided by peleyal some more and managed to find the issue. The problem was that I wasn't actually storing a refresh-token, I was storing an authentication token. I should've used the authentication token to generate the refresh token.
Here's the full solution in a simplified manner for future reference. The differences can be found in the GetAuthorization function, which now correctly saves the refresh token and opens a browser window to request the authorization when a refresh token is not yet available.
private static refreshToken = null;
public static void Main( string[] args )
{
// Register the authenticator.
NativeApplicationClient provider = new NativeApplicationClient(
GoogleAuthenticationServer.Description, gaAppId, gaSecret );
OAuth2Authenticator<NativeApplicationClient> auth = new OAuth2Authenticator<NativeApplicationClient>(
provider, GetAuthorization );
AnalyticsService analyticsService = new AnalyticsService( new BaseClientService.Initializer {
Authenticator = auth,
ApplicationName = #"Test Application",
} );
DataResource.GaResource.GetRequest request = analyticsService.Data.Ga.Get( gaId, #"2013-09-04",
#"2013-09-18", #"ga:totalEvents" );
GaData data = request.Execute();
Console.ReadKey();
}
private static IAuthorizationState GetAuthorization( NativeApplicationClient arg )
{
// Get the auth URL:
IAuthorizationState state = new AuthorizationState(
new[] {AnalyticsService.Scopes.AnalyticsReadonly.GetStringValue()} );
state.Callback = new Uri( NativeApplicationClient.OutOfBandCallbackUrl );
if( !string.IsNullOrEmpty( refreshToken ) ) {
try {
state.RefreshToken = refreshToken;
arg.RefreshToken( state );
} catch {
refreshToken = null;
}
}
// If the refresh token is empty, request a new one by opening
// a browser window. Allows the user to paste its authorization token
// and saves the refresh token.
if( string.IsNullOrEmpty( refreshToken ) ) {
Uri authUri = arg.RequestUserAuthorization( state );
// Request authorization from the user (by opening a browser window):
Process.Start( authUri.ToString() );
Console.Write( " Authorization Code: " );
string authCode = Console.ReadLine();
Console.WriteLine();
// Retrieve the access token by using the authorization code:
state = arg.ProcessUserAuthorization( authCode, state );
refreshToken = state.RefreshToken;
}
return state;
}

RestSharp on Windows Phone no set-cookie in response

I have a problem with using the RestSharp client on the Windows Phone device. Starting form the beginning, I have the ASP.NET Web Api service hosted online. I have a request user address: POST: http://my-service-url.com/token where I send Email and Password as a body parameters and I get 201 status code and a cookie in the response. When I do it in fiddler everything works fine. I also have the functional test for my API which is using the RestSharp which is also working correctly:
[Given(#"I fill email and password with correct data and I click log in button")]
public void GivenIFillEmailAndPasswordWithCorrectDataAndIClickLogInButton()
{
//Delete user if he exists
_testUserHelper.DeleteTestUser(TestEmail);
//Create new activated user
Assert.IsTrue(_testUserHelper.CreateTestUser(TestEmail, TestPassword));
//Prepare client
var client = new RestClient(CarRentalsConstants.HostAddress);
var restRequest = new RestRequest("api/token", Method.POST) { RequestFormat = DataFormat.Json };
//Add parameters to request
restRequest.AddBody(new { Email = TestEmail, Password = TestPassword });
//Perform request
_response = client.Execute(restRequest);
}
[When(#"the log in login process finishes")]
public void WhenTheLogInLoginProcessFinishes()
{
Assert.AreEqual(HttpStatusCode.Created, _response.StatusCode, _response.Content);
Assert.IsNotNull(_response.Cookies.SingleOrDefault(q => q.Name == ".ASPXAUTH"););
}
The one above works properly, and the cookie is in the response object.
Now what I try to do on my windows phone looks like this:
var restRequest = new RestRequest("token", Method.POST) {RequestFormat = DataFormat.Json};
restRequest.AddBody(new {Email = email, Password = password});
myWebClient.ExecuteAsync(restRequest, (restResponse, handle) =>
{
switch (restResponse.StatusCode)
{
case HttpStatusCode.Created:
{
var cookie = restResponse.Cookies.SingleOrDefault(q => q.Name == ".ASPXAUTH");
successLogicDelegate(cookie);
}
break;
case HttpStatusCode.BadRequest:
{
HandleBadRequest(restResponse.Content, failureLogicDelegate);
}
break;
default:
msgBox.Show(StringResources.ServerConnectionError);
failureLogicDelegate(null);
break;
}
});
And in this case, the response returns the "Created" status code, but the cookie is then set to null. I have no idea what is happening here, but I am fairly certain that server is sending this cookie, so where does it get lost?
Any help will be really appreciated.

Resources