nswag generated service has no return logic - web-services

I have a asp.net WebAPI service for user login that takes an email and password. The api method has the following signature. LoginDto has two fileds, Email and password.
public async Task<IActionResult> Login(LoginDto dto)
Once the user is authenticated, WebAPI returns an object that has token and Id:
return Ok(new { Token = GenerateJwtTokenFromClaims(claims), Id=user.Id });
On the client side (Blazor app), I used nswag command line tool by running nswag run and it "successfully" generated the Service and Contract files. Everything complies. nswag generated code is pasted below.
When I want to use the login nswag Service, I have the following method (I also have an overloaded method with CancellationToken but I only use this method):
public System.Threading.Tasks.Task Login2Async(LoginDto body)
{
return Login2Async(body, System.Threading.CancellationToken.None);
}
The question that I have is that how do I get the response out of the nswag-generated-code that the WebAPI login sent back to the client? When I try to assign a var to the method, I get Cannot assign void to an implicitly-typed variable which makes sense since I don't see a return type. I also don't see any logic in the nswag generated service file to return the response to the caller. How do I get the response back from the nswag generated API call? Is there an option I have to set in nswag run to get a response object back? Thanks in advance.
public async System.Threading.Tasks.Task Login2Async(LoginDto body, System.Threading.CancellationToken cancellationToken)
{
var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Account/Login");
var client_ = _httpClient;
var disposeClient_ = false;
try
{
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value));
content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
request_.Content = content_;
request_.Method = new System.Net.Http.HttpMethod("POST");
PrepareRequest(client_, request_, urlBuilder_);
var url_ = urlBuilder_.ToString();
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
PrepareRequest(client_, request_, url_);
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var disposeResponse_ = true;
try
{
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
if (response_.Content != null && response_.Content.Headers != null)
{
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}
ProcessResponse(client_, response_);
var status_ = (int)response_.StatusCode;
if (status_ == 200)
{
return;
}
else
if (status_ == 400)
{
var objectResponse_ = await ReadObjectResponseAsync<ProblemDetails>(response_, headers_).ConfigureAwait(false);
throw new ApiException<ProblemDetails>("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
}
else
{
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
}
}
finally
{
if (disposeResponse_)
response_.Dispose();
}
}
}
finally
{
if (disposeClient_)
client_.Dispose();
}
}

Big thanks to the NSwag team, the issue is resolved. I was returning anonymous object from the WebAPI method. The correct way to do is the following. Notice that IActionResult was changed to ActionResult passing a concrete object to return to the caller.
public async Task<ActionResult<LoginDtoResponse>> Login(LoginDto dto)
then returning
return Ok(new LoginDtoResponse { Token = GenerateJwtTokenFromClaims(claims), Id=user.Id });
After that I did that, the following code was generated:
if (status_ == 200)
{
var objectResponse_ = await ReadObjectResponseAsync<LoginDtoResponse>(response_, headers_).ConfigureAwait(false);
return objectResponse_.Object;
}

Related

Persist user session with cookie in Blazor WebAssembly

How can I persist user session between user usages of the application (closed browser/tab without logout)?
Can a Blazor WebAssembly app persist the user session in a cookie with some expiration date?
Right now I got this startup configuration, and everything related to authorization with IdentityServer works fine. The tokens in Identity Server have a 30 days expiration period.
// startup.cs
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Local", options.ProviderOptions);
options.UserOptions.RoleClaim = "RoleName";
});
// appsettings.json
"Local": {
"Authority": "https://idserver.url",
"ClientId": "Client",
"DefaultScopes": [
"openid",
"profile",
"email",
"roles",
"offline_access"
],
"ResponseType": "code",
"PostLogoutRedirectUri": "https://localhost:5004/authentication/logout-callback",
"RedirectUri": "https://localhost:5004/authentication/login-callback"
}
Is there a way to persist user session in cookies?
I will try my best to answer your question, because I had the same issue and figured it out.
So essentially in between sessions, the cookie is still there saved in the browser, and even though your AuthenticationStateProvider is not set to authenticated, if you try and execute a call against your API, the Cookie Handler will include the cookie in the request and it will authenticate.
So I was able to implement a solution based off an article that I found here:
https://www.learmoreseekmore.com/2022/04/blazorwasm-cookie-series-part-1-blazor-webassembly-cookie-authentication.html
I assume that you have a delegation handler that attaches the cookie to outgoing HTTP requests like so:
public class CookieHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
return await base.SendAsync(request, cancellationToken);
}
}
This guy will continue to attach that cookie in between sessions.
The problem is that the AuthenticationStateProvider will not persist his state. So what I did was save a local variable into the local browser storage that allows me to remember if I am authenticated. When I check if I am logged in, I make the following checks:
I check if the AutenticationStateProvider is authenticated.
If not, I check if I have set a local variable in the local storage indicating if I am authenticated. If that local variable exists, then he will make an API call to my web service asking for my user information. If that call completes successfully, then I update my AuthenticationStateProvider
I have a dependency injection service I call ILoginService that has an implementation that looks a bit like so:
ILocalStorageService _storageService;
AuthenticationStateProvider _authStateProvider;
public UserModel User { get; private set; } = new UserModel();
public LoginService(ILocalStorageService storageService, IHttpClientFactory clientFactory, AuthenticationStateProvider authStateProvider) : base(clientFactory)
{
_authStateProvider = authStateProvider;
_storageService = storageService;
}
public async Task<bool> IsLoggedIn()
{
var authState = await _authStateProvider.GetAuthenticationStateAsync();
if (authState.User?.Identity?.IsAuthenticated == true)
{
return true;
}
var isauthenticated = await _storageService.GetItemAsync<string>("isauthenticated");
if (!string.IsNullOrWhiteSpace(isauthenticated))
{
using (var client = _clientFactory.CreateClient("API"))
{
var response = await client.GetAsync("/login");
if (response.IsSuccessStatusCode)
{
string jsonStr = await response.Content.ReadAsStringAsync();
UserModel? user = JsonConvert.DeserializeObject<UserModel>(jsonStr);
if (user == null)
{
await _storageService.RemoveItemAsync("isauthenticated");
return false;
}
else
{
(_authStateProvider as CustomAuthStateProvider)?.SetAuthInfo(user);
this.User = user;
return true;
}
}
}
}
await _storageService.RemoveItemAsync("isauthenticated");
return false;
}
public async Task<bool> Login(LoginCredentials credentials)
{
try
{
if (credentials == null) throw new ArgumentNullException(nameof(credentials));
using (var client = _clientFactory.CreateClient("API"))
{
StringContent body = new StringContent(JsonConvert.SerializeObject(credentials), System.Text.Encoding.UTF8, "application/json");
var response = await client.PostAsync("/login", body);
if (response.IsSuccessStatusCode)
{
string jsonStr = await response.Content.ReadAsStringAsync();
UserModel? user = JsonConvert.DeserializeObject<UserModel>(jsonStr);
if (user == null)
{
await _storageService.RemoveItemAsync("isauthenticated");
return false;
}
else
{
(_authStateProvider as CustomAuthStateProvider)?.SetAuthInfo(user);
this.User = user;
await _storageService.SetItemAsync<string>("isauthenticated", "true");
return true;
}
}
else
{
#if DEBUG
string responseStr = await response.Content.ReadAsStringAsync();
#endif
await _storageService.RemoveItemAsync("isauthenticated");
return false;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
await _storageService.RemoveItemAsync("isauthenticated");
return false;
}
}
public async Task<bool> Logout()
{
try
{
using (var client = _clientFactory.CreateClient("API"))
{
var response = await client.DeleteAsync("/login");
if (!response.IsSuccessStatusCode)
{
#if DEBUG
string responseStr = await response.Content.ReadAsStringAsync();
#endif
}
}
(_authStateProvider as CustomAuthStateProvider)?.ClearAuthInfo();
await _storageService.RemoveItemAsync("isauthenticated");
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex);
return false;
}
}
I hope this helps you fix your problem and anybody else that comes along.

.NET Bearer Token cant [Authorize] always 401 Unauthorized

I'm very newbie in .NET and trying to implemented Bearer Token in some WebAPI:
My Controller:
public class ClientController : Controller
{
[Route("/api")]
[AllowAnonymous]
[HttpGet("{clientKey}")]
public async Task<IActionResult> UserLogin(string clientKey)
{
SQLDataResult response = null;
List<SqlParameter> parameters = new List<SqlParameter>();
parameters.Add(new SqlParameter("#clientKey", clientKey));
response = await new SQLDataContext().StoredProcedureExecuteReaderAsync<SQLDataResult>("ClientCheck", parameters);
if (response == null)
{
response = new SQLDataResult();
return Ok(response);
}
else
{
var token = new JwtTokenBuilder()
.AddSecurityKey(JwtSecurityKey.Create("imarh-secret-key"))
.AddSubject("imarh")
.AddIssuer("imarh.Security.Bearer")
.AddAudience("imarh.Security.Bearer")
.AddClaim("MembershipId", "111")
.AddExpiry(1)
.Build();
return Ok(token.Value);
}
}
[Authorize]
[HttpPost]
[Route("/api/get/{clientID}")]
public async Task<IActionResult> ClientAssign(string clientID)
{
SQLDataResult response = null;
List<SqlParameter> parameters = new List<SqlParameter>();
parameters.Add(new SqlParameter("#clientID", clientID));
response = await new SQLDataContext().StoredProcedureExecuteReaderAsync<SQLDataResult>("ClientAssign", parameters);
if (response == null)
response = new SQLDataResult();
return Ok(response);
}
}
Token is getting correctly. When I remove [Authorize] attribute from second API I will get correct result from the database. But when return and pass generated token in Authorization header I always getting 401 Unauthorized with no any result.
Suppose this can happen in different cases, but in mine the only one that fixes the problem was:
// app.UseMvc();
in Startup.cs

Want to get the details of the user(member) after login successfully in xamarin forms

my question is how to pass username and password from the C# client(xamarin forms) to server's API? if details are correct then the client will get whole product list from webapi(URL).and bind all the details to a listview.I want to get the member details after the success of response code.
the client will send username password from login page to server's API. if server's webapi check whether the details matched with the database, if not, don't let it get product list.
here is the code in loginservices for login(xamarin forms)
public async Task GetData(string username,string password)
{
//string detail = new UserDetails();
UserDetails userDetails = new UserDetails();
// List<UserDetails> detail = new List<UserDetails>();
try
{
var values = new List<KeyValuePair<string, string>>();
values.Add(new KeyValuePair<string, string>("Username", username));
values.Add(new KeyValuePair<string, string>("Password", password));
var content = new FormUrlEncodedContent(values);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("nl-NL"));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.PostAsync("http://192.168.1.50/Accounts/Authenticate", content);
return response.IsSuccessStatusCode;
};
}
catch (Exception ex)
{
throw ex;
}
}
here is the code for web api---
public async Task ValidateUser([FromBody] Credentials credentials)
{
using (DemoAPPEntities entities = new DemoAPPEntities())
{
var result = await entities.MemberDetails.Where(x => x.UserName == credentials.UserName && x.Password == credentials.Password).SingleOrDefaultAsync();
if (result == null)
{
return NotFound();
}
return Ok(entities.MemberDetails);
}
}

Moq - Setup method with complex parameters

I'm trying to set up a mock of this interface:
public interface IAuthenticatedRequestService
{
HttpClient CreateHttpClientForJwt(Func<HttpResponseMessage, bool> isUnauthenticated, int timeoutSeconds);
HttpClient CreateHttpClientForAccessToken(Func<HttpResponseMessage, bool> isUnauthenticated, int timeoutSeconds);
}
This is one implementation of the method to setup that is in use and working:
public HttpClient CreateHttpClientForAccessToken(Func<HttpResponseMessage, bool> isUnauthenticated, int timeoutSeconds)
{
var client = Mvx.Resolve<IPlatformOperationProvider>().CreateHttpClient(timeoutSeconds);
return new HttpClient(new AuthenticatedHttpMessageHandler(this, client, AuthenticationUtils.AddAccessTokenToRequest, isUnauthenticated,_loggingService));
}
This is one usage of the implemented method that is working:
var client = service.CreateHttpClientForAccessToken(x => x.StatusCode == HttpStatusCode.Unauthorized, CoreConstants.TimeoutMyDetails);
This is my unit test which sets up the mock:
[Test]
public async void TestIsLoggedInIsTrue()
{
//Arrange
var authenticatedRequestService = new Mock<IAuthenticatedRequestService>();
authenticatedRequestService.Setup(foo => foo.CreateHttpClientForAccessToken((It.IsAny<Func <HttpResponseMessage, bool>>())
, 0
)).Returns(new HttpClient());
var platformOperationProvider = new Mock<IPlatformOperationProvider>();
platformOperationProvider.Setup(foo => foo.CreateHttpClient(1)).Returns(new HttpClient());
Mvx.RegisterSingleton<IPlatformOperationProvider>(platformOperationProvider.Object);
Mvx.RegisterSingleton<IAuthenticatedRequestService>(authenticatedRequestService.Object);
var loggedInProvider = new LoggedInProvider(
new Mock<ISecuredSettings>().Object,
new Mock<ILoggingService>().Object
);
//Act
await loggedInProvider.SetUserAndToken(
new User(),
new ApiAccessInfo("refresh token", "access token", "jwt")
);
//Assert
Assert.IsTrue(loggedInProvider.IsLoggedIn);
}
This unit test has no errors, but the test fails (I think it is because I am passing it any HttpResponseMessage? And I need to somehow pass it HttpStatusCode.Accepted? How would I do that?
Take note of the usage of the method, how it passes HttpStatusCode.Unauthorized, then can I do something like that with HttpStatusCode.Accepted?:
var client = service.CreateHttpClientForAccessToken(x => x.StatusCode == HttpStatusCode.Unauthorized, CoreConstants.TimeoutMyDetails);
EDIT: To be clear, It is this line of code that I need to correct:
authenticatedRequestService.Setup(foo => foo.CreateHttpClientForAccessToken((It.IsAny<Func <HttpResponseMessage, bool>>())
, 0
)).Returns(new HttpClient());
EDIT: Whilst debugging the problem starts here (check the code comment after the client is created):
async Task<ServiceResponse> UpdateUserDetails()
{
// Have to late-resolve this otherwise we end up with a dependency loop
var service = Mvx.Resolve<IAuthenticatedRequestService>();
try
{
var client = service.CreateHttpClientForAccessToken(x => x.StatusCode == HttpStatusCode.Unauthorized, CoreConstants.TimeoutMyDetails);
// here is the problem, the client is null after this line of code.
var user = _user;
I have since found that it is not a Moq issue. MvvmCross is not registering the object to resolve correctly.
This line is not working:
Mvx.RegisterSingleton<IAuthenticatedRequestService>(authenticatedRequestService.Object);
as this line creates an AuthenticatedRequestService but it is not the mock one that I made:
var service = Mvx.Resolve<IAuthenticatedRequestService>();
Here is some context of resolving the AuthenticatedRequestService
async Task<ServiceResponse> UpdateUserDetails()
{
// Have to late-resolve this otherwise we end up with a dependency loop
var service = Mvx.Resolve<IAuthenticatedRequestService>();
try
{
var client = service.CreateHttpClientForAccessToken(x => x.StatusCode == HttpStatusCode.Unauthorized, CoreConstants.TimeoutMyDetails);
var user = _user;
var str = await client.GetStringAsync(new Uri(user.IdUrl));
var newUser = JsonConvert.DeserializeObject<User.Json>(str);
var token = _token;
if (token != null)

How to set content-length-range for s3 browser upload via boto

The Issue
I'm trying to upload images directly to S3 from the browser and am getting stuck applying the content-length-range permission via boto's S3Connection.generate_url method.
There's plenty of information about signing POST forms, setting policies in general and even a heroku method for doing a similar submission. What I can't figure out for the life of me is how to add the "content-length-range" to the signed url.
With boto's generate_url method (example below), I can specify policy headers and have got it working for normal uploads. What I can't seem to add is a policy restriction on max file size.
Server Signing Code
## django request handler
from boto.s3.connection import S3Connection
from django.conf import settings
from django.http import HttpResponse
import mimetypes
import json
conn = S3Connection(settings.S3_ACCESS_KEY, settings.S3_SECRET_KEY)
object_name = request.GET['objectName']
content_type = mimetypes.guess_type(object_name)[0]
signed_url = conn.generate_url(
expires_in = 300,
method = "PUT",
bucket = settings.BUCKET_NAME,
key = object_name,
headers = {'Content-Type': content_type, 'x-amz-acl':'public-read'})
return HttpResponse(json.dumps({'signedUrl': signed_url}))
On the client, I'm using the ReactS3Uploader which is based on tadruj's s3upload.js script. It shouldn't be affecting anything as it seems to just pass along whatever the signedUrls covers, but copied below for simplicity.
ReactS3Uploader JS Code (simplified)
uploadFile: function() {
new S3Upload({
fileElement: this.getDOMNode(),
signingUrl: /api/get_signing_url/,
onProgress: this.props.onProgress,
onFinishS3Put: this.props.onFinish,
onError: this.props.onError
});
},
render: function() {
return this.transferPropsTo(
React.DOM.input({type: 'file', onChange: this.uploadFile})
);
}
S3upload.js
S3Upload.prototype.signingUrl = '/sign-s3';
S3Upload.prototype.fileElement = null;
S3Upload.prototype.onFinishS3Put = function(signResult) {
return console.log('base.onFinishS3Put()', signResult.publicUrl);
};
S3Upload.prototype.onProgress = function(percent, status) {
return console.log('base.onProgress()', percent, status);
};
S3Upload.prototype.onError = function(status) {
return console.log('base.onError()', status);
};
function S3Upload(options) {
if (options == null) {
options = {};
}
for (option in options) {
if (options.hasOwnProperty(option)) {
this[option] = options[option];
}
}
this.handleFileSelect(this.fileElement);
}
S3Upload.prototype.handleFileSelect = function(fileElement) {
this.onProgress(0, 'Upload started.');
var files = fileElement.files;
var result = [];
for (var i=0; i < files.length; i++) {
var f = files[i];
result.push(this.uploadFile(f));
}
return result;
};
S3Upload.prototype.createCORSRequest = function(method, url) {
var xhr = new XMLHttpRequest();
if (xhr.withCredentials != null) {
xhr.open(method, url, true);
}
else if (typeof XDomainRequest !== "undefined") {
xhr = new XDomainRequest();
xhr.open(method, url);
}
else {
xhr = null;
}
return xhr;
};
S3Upload.prototype.executeOnSignedUrl = function(file, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', this.signingUrl + '&objectName=' + file.name, true);
xhr.overrideMimeType && xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var result;
try {
result = JSON.parse(xhr.responseText);
} catch (error) {
this.onError('Invalid signing server response JSON: ' + xhr.responseText);
return false;
}
return callback(result);
} else if (xhr.readyState === 4 && xhr.status !== 200) {
return this.onError('Could not contact request signing server. Status = ' + xhr.status);
}
}.bind(this);
return xhr.send();
};
S3Upload.prototype.uploadToS3 = function(file, signResult) {
var xhr = this.createCORSRequest('PUT', signResult.signedUrl);
if (!xhr) {
this.onError('CORS not supported');
} else {
xhr.onload = function() {
if (xhr.status === 200) {
this.onProgress(100, 'Upload completed.');
return this.onFinishS3Put(signResult);
} else {
return this.onError('Upload error: ' + xhr.status);
}
}.bind(this);
xhr.onerror = function() {
return this.onError('XHR error.');
}.bind(this);
xhr.upload.onprogress = function(e) {
var percentLoaded;
if (e.lengthComputable) {
percentLoaded = Math.round((e.loaded / e.total) * 100);
return this.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing.' : 'Uploading.');
}
}.bind(this);
}
xhr.setRequestHeader('Content-Type', file.type);
xhr.setRequestHeader('x-amz-acl', 'public-read');
return xhr.send(file);
};
S3Upload.prototype.uploadFile = function(file) {
return this.executeOnSignedUrl(file, function(signResult) {
return this.uploadToS3(file, signResult);
}.bind(this));
};
module.exports = S3Upload;
Any help would be greatly appreciated here as I've been banging my head against the wall for quite a few hours now.
You can't add it to a signed PUT URL. This only works with the signed policy that goes along with a POST because the two mechanisms are very different.
Signing a URL is a lossy (for lack of a better term) process. You generate the string to sign, then sign it. You send the signature with the request, but you discard and do not send the string to sign. S3 then reconstructs what the string to sign should have been, for the request it receives, and generates the signature you should have sent with that request. There's only one correct answer, and S3 doesn't know what string you actually signed. The signature matches, or doesn't, either because you built the string to sign incorrectly, or your credentials don't match, and it doesn't know which of these possibilities is the case. It only knows, based on the request you sent, the string you should have signed and what the signature should have been.
With that in mind, for content-length-range to work with a signed URL, the client would need to actually send such a header with the request... which doesn't make a lot of sense.
Conversely, with POST uploads, there is more information communicated to S3. It's not only going on whether your signature is valid, it also has your policy document... so it's possible to include directives -- policies -- with the request. They are protected from alteration by the signature, but they aren't encrypted or hashed -- the entire policy is readable by S3 (so, by contrast, we'll call this the opposite, "lossless.")
This difference is why you can't do what you are trying to do with PUT while you can with POST.