Call ServiceStack API Programmatically - servicestack

What are the best ways to call a servicestack API from asp.net website. Service is running in IIS.
All the methods inside the service stack require authentication first.
I tried with JsonServiceClient and HttpWebRequest. First time when I authenticate, service gives me cookies
ss-id and ss-pid and I store in cookies collection. Now when I request another method It says, You are not authorised.
The problem is, in the second request Cookies are not maintained. However if you test the service from browser it self. It do create cookies first during Authorization and in second request, It gives you proper response.
Below is my code. With JsonServiceClient and HttpWebRequest
[HttpPost]
public ActionResult Index(Login loginModel)
{
#region ServiceStack Call
HttpCookie ck;
string baseUrl = "http://192.168.1.101";
var client = new JsonServiceClient(baseUrl);
var authResponse = client.Send<AuthenticateResponse>(new Authenticate
{
UserName = loginModel.UserName,
Password = loginModel.Password,
RememberMe = true
});
foreach (System.Net.Cookie cookie in client.CookieContainer.GetCookies(new Uri(baseUrl)))
{
if (cookie.Name == "ss-id")
{
ck = new HttpCookie(cookie.Name);
ck.Value = cookie.Value;
ck.Expires.AddDays(1); //Check when to expire the cookie
Response.Cookies.Add(ck);
}
}
}
Below Code with HttpWebRequest
protected string CallToApi(string baseUrl, bool createCookie)
{
CookieContainer cc = new CookieContainer();
System.Uri uri = new Uri(baseUrl);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.ContentType = #"application/json; charset=utf-8";
request.Timeout = 200000;
request.Accept = "application/json";
request.CookieContainer = new CookieContainer();
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
StreamReader reader = new StreamReader(responseStream, System.Text.Encoding.UTF8);
if (reader != null)
{
if (createCookie)
{
//Create Cookies
}
}
return reader.ReadToEnd();
}
}
catch (WebException ex)
{
throw ex;
}
}
How to give call to second method?
http://192.168.1.101/api/teamleaders URL
When calling this Url, Can I persist my cookies? or there must be out of the box in ServiceStack itself.

The JsonServiceClient should persist cookies. I've used the following code (with the default CredentialsAuthProvider) successfully:
var client = new JsonServiceClient(baseUri);
var authResponse = client.Post(new Auth
{
provider = CredentialsAuthProvider.Name,
UserName = "username",
Password = "password",
RememberMe = true
});
Note: this is with version 3.9.71, NOT the new v4 stack which I haven't yet had the opportunity to upgrade to. The same 'should' work with a custom auth provider inheriting from CredentialsAuthProvider.

Related

Blazor Server SignalR Chat works on Local, not on Azure

I have a working Blazor Server application with a Chat component running locally correctly, using signal R hub connection.
When deploying to Azure, I receive error Invalid negotiation response received.---> System.Text.Json.JsonReaderException: '0x1F' is an invalid start of a value.
Here is a similar linked ticket, but it was never answered: Blazor Server SignalR hub fails on StartAsync due to Azure ADB2C
Goal: Create a private chat feature for Blazor server application. I am unable to use singleton service because all users cant share the same instance of the service.
I have yet to find a sample where there is a messaging feature between users/usergroups in blazor server.
Since I am using Azure B2C auth with OIDC default authentication scheme, I have to manually pass the cookies and the headers.
Like I mentioned, this sample is working perfectly on localhost, when I open up two browsers (one in incognito), i am able to send messages between logged in users. When I publish to Azure App Service, however, I am unable to connect to the hub.
Code:
private HubConnection _hubConnection;
private User user;
ObservableCollection<Message> messages = new ObservableCollection<Message>();
SfTextBox MessageBox;
SfTextBox SendTo;
public class Message
{
public string Id { get; set; }
public string UserName { get; set; }
public string MessageText { get; set; }
public string Chat { get; set; }
}
protected override async Task OnInitializedAsync()
{
var state = await authenticationStateProvider.GetAuthenticationStateAsync();
user = state.ToUser();
_hubConnection = new HubConnectionBuilder()
.WithUrl(navigationManager.ToAbsoluteUri("/chatHub"), options =>
{
options.UseDefaultCredentials = true;
var httpContext = HttpContextAccessor.HttpContext;
var cookieCount = httpContext.Request.Cookies.Count();
var cookieContainer = new System.Net.CookieContainer(cookieCount);
foreach (var cookie in httpContext.Request.Cookies)
cookieContainer.Add(new System.Net.Cookie(
cookie.Key,
WebUtility.UrlEncode(cookie.Value),
path: httpContext.Request.Path,
domain: httpContext.Request.Host.Host));
options.Cookies = cookieContainer;
NameValueHeaderValue headers = null;
foreach (var header in httpContext.Request.Headers)
{
if (header.Key != ":method")
options.Headers.Add(header.Key, header.Value);
}
options.HttpMessageHandlerFactory = (input) =>
{
var clientHandler = new HttpClientHandler
{
PreAuthenticate = true,
CookieContainer = cookieContainer,
UseCookies = true,
UseDefaultCredentials = true,
};
return clientHandler;
};
})
.WithAutomaticReconnect()
.Build();
_hubConnection.On<string, string, string, string>("ReceiveMessage", (userName, from, to, message) =>
{
if (user.Email == to || user.Id == from)
{
messages.Add(new Message()
{
Id = Guid.NewGuid().ToString(),
MessageText = message,
Chat = user.Id == from ? "sender" : "receive",
UserName = user.Id == from ? "You" : userName
});
StateHasChanged();
}
});
await _hubConnection.StartAsync();
}
public async void Send()
{
if (MessageBox.Value != "" && SendTo.Value != "")
{
var userName = user.DisplayName;
var to = SendTo.Value;
var message = MessageBox.Value;
var from = user.Id;
_hubConnection.SendAsync("SendMessage", userName, from, to, message);
}
}
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
}
The issue is with authentication on azure platform. As you used manually giving cookies it worked fine on local, and when it comes to Azure platform, we need to provide authentication.
Follow the below link for support.
https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-invalid-json?pivots=dotnet-6-0
https://learn.microsoft.com/en-us/azure/azure-signalr/signalr-tutorial-build-blazor-server-chat-app
HttpContextAccessor.HttpContext is where the problem is.
It works on your local (IIS Express), but it does not work when the application is deployed to Azure (or IIS).
I posted a solution here: Solution: Custom SignalR Endpoints (Hubs) in a Blazor Server Application using Azure B2C When Deployed to Azure
Basically:
Grab all the Cookies at this point and put them in a collection of Cookies so they can be passed to the app.razor control.
<body>
#{
var CookieCollection = HttpContext.Request.Cookies;
Dictionary<string, string> Cookies = new Dictionary<string, string>();
foreach (var cookie in CookieCollection)
{
Cookies.Add(cookie.Key, cookie.Value);
}
}
<component type="typeof(App)" render-mode="ServerPrerendered" param-Cookies="Cookies" />
Set them as a CascadingValue that will be passed to all other Blazor controls.
Retrieve the collection of Cookies as a CascadingParameter and use those Cookies to manually set the Header and Cookies when creating the SignalR client.
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"), options =>
{
options.UseDefaultCredentials = true;
var cookieCount = Cookies.Count();
var cookieContainer = new CookieContainer(cookieCount);
foreach (var cookie in Cookies)
cookieContainer.Add(new Cookie(
cookie.Key,
WebUtility.UrlEncode(cookie.Value),
path: "/",
domain: Navigation.ToAbsoluteUri("/").Host));
options.Cookies = cookieContainer;
foreach (var header in Cookies)
options.Headers.Add(header.Key, header.Value);
options.HttpMessageHandlerFactory = (input) =>
{
var clientHandler = new HttpClientHandler
{
PreAuthenticate = true,
CookieContainer = cookieContainer,
UseCookies = true,
UseDefaultCredentials = true,
};
return clientHandler;
};
})
.WithAutomaticReconnect()
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
InvokeAsync(StateHasChanged);
});
await hubConnection.StartAsync();

How can I use basic authentication with MobileServiceClient?

I'm using the azure mobile services sdk to do offline sync. I made my api so that it is protected with basic authentication using email and password.
How can I embed these credentials with the MobileServiceClient, so that whenever I call a method it has the correct auth credentials.
this is my existing code for the MobileServiceClient.
var handler = new AuthHandler();
//TODO 1: Create our client
//Create our client
MobileService = new MobileServiceClient(Helpers.Keys.AzureServiceUrl, handler)
{
SerializerSettings = new MobileServiceJsonSerializerSettings()
{
CamelCasePropertyNames = true
}
};
//assign mobile client to handler
handler.Client = MobileService;
MobileService.CurrentUser = new MobileServiceUser(Settings.UserId);
MobileService.CurrentUser.MobileServiceAuthenticationToken = Settings.AuthToken;
AuthHandler Class
class AuthHandler : DelegatingHandler
{
public IMobileServiceClient Client { get; set; }
private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(1);
private static bool isReauthenticating = false;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//Clone the request in case we need to send it again
var clonedRequest = await CloneRequest(request);
var response = await base.SendAsync(clonedRequest, cancellationToken);
//If the token is expired or is invalid, then we need to either refresh the token or prompt the user to log back in
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
if (isReauthenticating)
return response;
var service = DependencyService.Get<AzureService>();
var client = new MobileServiceClient(Helpers.Keys.AzureServiceUrl, null);
client.CurrentUser = new MobileServiceUser(Settings.UserId);
client.CurrentUser.MobileServiceAuthenticationToken = Settings.AuthToken;
string authToken = client.CurrentUser.MobileServiceAuthenticationToken;
await semaphore.WaitAsync();
//In case two threads enter this method at the same time, only one should do the refresh (or re-login), the other should just resend the request with an updated header.
if (authToken != client.CurrentUser.MobileServiceAuthenticationToken) // token was already renewed
{
semaphore.Release();
return await ResendRequest(client, request, cancellationToken);
}
isReauthenticating = true;
bool gotNewToken = false;
try
{
gotNewToken = await RefreshToken(client);
//Otherwise if refreshing the token failed or Facebook\Twitter is being used, prompt the user to log back in via the login screen
if (!gotNewToken)
{
gotNewToken = await service.LoginAsync();
}
}
catch (System.Exception e)
{
Debug.WriteLine("Unable to refresh token: " + e);
}
finally
{
isReauthenticating = false;
semaphore.Release();
}
if (gotNewToken)
{
if (!request.RequestUri.OriginalString.Contains("/.auth/me")) //do not resend in this case since we're not using the return value of auth/me
{
//Resend the request since the user has successfully logged in and return the response
return await ResendRequest(client, request, cancellationToken);
}
}
}
return response;
}
private async Task<HttpResponseMessage> ResendRequest(IMobileServiceClient client, HttpRequestMessage request, CancellationToken cancellationToken)
{
// Clone the request
var clonedRequest = await CloneRequest(request);
// Set the authentication header
clonedRequest.Headers.Remove("X-ZUMO-AUTH");
clonedRequest.Headers.Add("X-ZUMO-AUTH", client.CurrentUser.MobileServiceAuthenticationToken);
// Resend the request
return await base.SendAsync(clonedRequest, cancellationToken);
}
private async Task<bool> RefreshToken(IMobileServiceClient client)
{
var authentication = DependencyService.Get<IAuthentication>();
if (authentication == null)
{
throw new InvalidOperationException("Make sure the ServiceLocator has an instance of IAuthentication");
}
try
{
return await authentication.RefreshUser(client);
}
catch (System.Exception e)
{
Debug.WriteLine("Unable to refresh user: " + e);
}
return false;
}
private async Task<HttpRequestMessage> CloneRequest(HttpRequestMessage request)
{
var result = new HttpRequestMessage(request.Method, request.RequestUri);
foreach (var header in request.Headers)
{
result.Headers.Add(header.Key, header.Value);
}
if (request.Content != null && request.Content.Headers.ContentType != null)
{
var requestBody = await request.Content.ReadAsStringAsync();
var mediaType = request.Content.Headers.ContentType.MediaType;
result.Content = new StringContent(requestBody, Encoding.UTF8, mediaType);
foreach (var header in request.Content.Headers)
{
if (!header.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase))
{
result.Content.Headers.Add(header.Key, header.Value);
}
}
}
return result;
}
}
How can I embed these credentials with the MobileServiceClient, so that whenever I call a method it has the correct auth credentials.
Per my understanding, the AuthHandler class could provide a method for setting the current valid user info after the user has successfully logged in with the correct email and password. Also, you need to cache the AuthHandler instance which is used to construct the MobileServiceClient instance, after user logged, you could embed the current user info into the AuthHandler instance.
If you are talking about providing a sign-in process with a username and password rather than using a social provider, you could just follow Custom Authentication for building your CustomAuthController to work with App Service Authentication / Authorization (EasyAuth). For your client, you could use the following code for logging:
MobileServiceUser azureUser = await _client.LoginAsync("custom", JObject.FromObject(account));
Moreover, you need to cache the MobileServiceAuthenticationToken issued by your mobile app backend and manually valid the cached token and check the exp property of the JWT token under the SendAsync method of your AuthHandler class, and explicitly call LoginAsync with the cached user account for acquiring the new MobileServiceAuthenticationToken when the current token would be expired soon or has expired without asking the user to log in again. Detailed code sample, you could follow adrian hall's book about Caching Tokens.
Or if you are talking about Basic access authentication, you could also refer the previous part about embedding credentials into your AuthHandler. For your server-side, you could also add your custom DelegatingHandler to validate the authorization header and set the related Principal to HttpContext.Current.User. And you could initialize your DelegatingHandler under Startup.MobileApp.cs file as follows:
HttpConfiguration config = new HttpConfiguration();
config.MessageHandlers.Add(new MessageHandlerBasicAuthentication());
Moreover, you could follow Basic Authentication Using Message Handlers In Web API.

Sending custom parameter on authentication

I'm a servicestack newbie. I'm trying to figure out how to send custom parameter on authentication.
As far as I understood, that's the step to authenticate a client and than execute a set of call within a session
var jsonClient = new JsonServiceClient("http://localhost:55679/");
var authResponse = client.Send(new Authenticate
{
provider = "myProvider",
UserName = "user",
Password = "pwd",
RememberMe = true,
});
var jResponse = jsonClient.Get<CountriesResponse>(request);
Console.WriteLine(jResponse.Countries.Count);
So far so good, I configurated my apphost as following and everything works as expected.
Plugins.Add(new AuthFeature(() => new CustomUserSession(), new IAuthProvider[] {
new MyAuthProvider(),
}));
What should I do if, instead of sending ServiceStack.Authenticate, I'd like to send my MyAuthenticate
request that has same custom properties, somenthing like this?
var authResponse = client.Send(new MyAuthenticate
{
provider = "myProvider",
UserName = "user",
Password = "pwd",
RememberMe = true,
AppId = "AppId",
ProjectId = "ProjectId"
});
My goal is to send custom parameter while I'm authenticating the user, not just those allowed by Authenticate built-in request, and than store those extra parameter within my CustomUserSession.
Thanks
Sending additional info on QueryString or HttpHeaders
As you can't change the built-in Authenticate Request DTO, one way to send additional metadata is to add extra info on the QueryString or HTTP Headers.
If you wanted to use the .NET Service Clients to do this you would need to use the RequestFilter, e.g:
var client = new JsonServiceClient(BaseUrl) {
RequestFilter = req => {
req.QueryString["AppId"] = appId;
req.QueryString["ProjectId"] = appId;
}
};
var authResponse = client.Send(new Authenticate { ... });
Otherwise creating custom Request is often more flexible using ServiceStack's built-in HTTP Utils, e.g:
var url = "{0}/auth/myProvider".Fmt(BaseUrl)
.AddQueryParam("AppId", appId)
.AddQueryParam("ProjectId", projectId);
var authResponse = url.PostJsonToUrl(new Authenticate { ... });
On the server the additional data will be available in the QueryString of the current request which you can get from IServiceBase or IRequest args, e.g:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
...
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
...
var customSession = (CustomUserSession)session;
customSession.AppId = authService.Request.QueryString["AppId"];
customSession.ProjectId = authService.Request.QueryString["ProjectId"];
return base.OnAuthenticated(authService, session, tokens, authInfo);
}
}
Custom Meta dictionary now available on Authenticate Request DTO
To make this use-case a little easier a new Dictionary<string,string> Meta property was added on the Authenticate DTO which makes calling from the Typed Service Clients a little nicer since you don't have to use a filter, e.g:
var client = new JsonServiceClient(BaseUrl);
var authResponse = client.Send(new Authenticate {
...
Meta = new Dictionary<string, string> { {"AppId", appId}, {"ProjectId", pId} },
});
Which you can access from the Authenticate DTO directly, e.g:
var authRequest = (Authenticate)authService.Request.Dto;
customSession.AppId = authRequest.Meta["AppId"];
customSession.ProjectId = authRequest.Meta["ProjectId"];
The new Meta property is available from v4.0.35+ that's currently available on MyGet.
Use your own Custom Authentication Service
A more disruptive alternative approach to be able to use your own MyAuthenticate DTO is to handle the authentication request in your own Service and then delegate to the AuthService, e.g:
public class MyAuthenticate : Authenticate
{
public string AppId { get; set; }
public string ProjectId { get; set; }
}
public class MyAuthServices : Service
{
public object Any(MyAuthenticate request)
{
using (var auth = base.ResolveService<AuthenticateService>())
{
var response = auth.Post(request);
var authResponse = response as AuthenticateResponse;
if (authResponse != null) {
var session = base.SessionAs<CustomUserSession>();
session.AppId = request.AppId;
session.ProjectId = request.ProjectId;
this.SaveSession(session);
}
return response;
}
}
}

Return a custom auth response object from ServiceStack authentication

Is it possible to return a custom auth response? I already have my own custom authentication provider that inherits from CredentialsAuthProvider.
I want to return the session expiry date in the response, so that the client knows exactly when their server session will expire:
{
"sessionId": "bG27SdxbRkqJqU6xv/gvBw==",
"userName": "joe.bloggs#letmein.com",
"sessionExpires": "2013-04-29T03:27:14.0000000",
"responseStatus": {}
}
I can override the Authenticate method like so:
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
// get base response
var response = base.Authenticate(authService, session, request);
// grab the session
var customSession = authService.GetSession() as CustomUserSession;
// if response can be cast and customSession exists
if (response is AuthResponse && customSession != null)
{
// cast
var authResponse = response as AuthResponse;
// build custom response
var customAuthResponse = new CustomAuthResponse
{
ReferrerUrl = authResponse.ReferrerUrl,
SessionExpiry = customSession.SessionExpires,
SessionId = authResponse.SessionId,
ResponseStatus = authResponse.ResponseStatus,
UserName = authResponse.UserName
};
return customAuthResponse;
}
// return the standard response
return response;
}
This works fine, except in the case where the session already is active. In that case, the AuthService Post method checks for a valid session and automatically returns a standard AuthResponse, and there is no obvious way to override it:
var alreadyAuthenticated = response == null;
response = response ?? new AuthResponse {
UserName = session.UserAuthName,
SessionId = session.Id,
ReferrerUrl = referrerUrl,
};
Following Paaschpa's ideas below, the following forces re-auth to always be re-authenticated, but it seems like there could be risks involved in leaving multiple active sessions open:
public override bool IsAuthorized(IAuthSession session, IOAuthTokens tokens, Auth request = null)
{
// force re-authentication. Not great, but no other obvious way to do this
if (request != null)
{
return false; // auth or re-auth calls
}
return base.IsAuthorized(session, tokens, request);
}
Can anyone think of a better way to do this? I could implement my own AuthenticationService, but I'm not sure how I would override the AuthFeature?
If I understand correctly, you want to return a custom response after a user authenticates against '/auth/credentials'. Since you already have your own CredentialsAuthProvider I think you could just override Authenticate and return your own response.
Subclass of CredentialsAuthProvider
public class MyCredentialsAuthProvider : CredentialsAuthProvider
{
public override object Authenticate(ServiceStack.ServiceInterface.IServiceBase authService, IAuthSession session, Auth request)
{
//let normal authentication happen
var authResponse = (AuthResponse)base.Authenticate(authService, session, request);
//return your own class, but take neccessary data from AuthResponse
return new
{
UserName = authResponse.UserName,
SessionId = authResponse.SessionId,
ReferrerUrl = authResponse.ReferrerUrl,
SessionExpires = DateTime.Now
};
}
}

ServiceStack - Custom CredentialsAuthProvider within .Net MVC app

I am attempting to authenticate against MVC and ServiceStack following the example here - https://github.com/ServiceStack/ServiceStack.UseCases/tree/master/CustomAuthenticationMvc.
My issue is that I am unable to authenticate successfully against ServiceStack on my initial request to Account/LogOn.
ServiceStack related code in LogOn method of AccountController:
var apiAuthService = AppHostBase.Resolve<AuthService>();
apiAuthService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
var apiResponse = apiAuthService.Authenticate(new Auth
{
UserName = model.UserName,
Password = model.Password,
RememberMe = false
});
I have a custom Authentication Provider that subclasses CredentialsAuthProvider. I Configure as follows in the AppHost class:
var appSettings = new AppSettings();
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new ActiveDirectoryAuthProvider(),
}));
public override bool TryAuthenticate(ServiceStack.ServiceInterface.IServiceBase authService, string userName, string password)
{
//class to authenticate against ActiveDirectory
var adAuthentication = new ActiveDirectoryAuthenticationService();
if (!adAuthentication.Authenticate(userName, password))
return false;
var session = (CustomUserSession)authService.GetSession(false);
session.IsAuthenticated = true;
session.UserAuthId = session.UserAuthName;
authService.SaveSession(session, SessionExpiry);
return true;
}
I think my issue is that session.Id is null at this point and saving the session persists 'urn:iauthsession:' to the 'SessionCache'. However, I'm not sure how to correctly populate session.Id. Also, this may or may not be an issue, but the initial LogOn request is to Account/Logon which is handled by MVC. So, there is no request to ServiceStack prior to the AuthService.Authenticate() call in the AccountController.
A possible solution I came up with has been added below in my subclass of CredentialsAuthProvider.
public override bool TryAuthenticate(ServiceStack.ServiceInterface.IServiceBase authService, string userName, string password)
{
//class to authenticate against ActiveDirectory
var adAuthentication = new ActiveDirectoryAuthenticationService();
if (!adAuthentication.Authenticate(userName, password))
return false;
var session = (CustomUserSession)authService.GetSession(false);
//A possible solution???
if(session.Id == null)
{
var req = authService.RequestContext.Get<IHttpRequest>();
var sessId = HttpContext.Current.Response.ToResponse().CreateSessionIds(req);
session.Id = sessId;
req.SetItem(SessionFeature.SessionId, sessId);
}
//end possible solution
session.IsAuthenticated = true;
session.UserAuthId = session.UserAuthName;
authService.SaveSession(session, SessionExpiry);
return true;
}
Is there a configuration or call I'm missing to 'wire up' ServiceStack Authentication within MVC?
Thanks.
The only thing I am doing in my TryAuthenticate is validating the user name, password and returning true if valid.
I have another override method called OnAuthenticated where I am saving the session information. OnAuthenticated passes the Auth Service and the Session as parameter so you only have to:
public override void OnAuthenticated(IServiceBase authService, IAuthSession session,.....
{
session.IsAuthenticated = true;
session.....
authService.SaveSession(session, SessionExpiry);
}
This seems to store my session information as long as I registered the ICacheClient.

Resources