ServiceStack with IdentityServer - servicestack

Using the template at https://github.com/NetCoreTemplates/mvcidentityserver
and trying to require Authorization to access the ServiceStack "Hello" object.
I'm able to retrieve a token and call
https://localhost:5001/servicestack-identity
and
https://localhost:5001/webapi-identity
from PostMan successfully.
However, if I add an Authenticate attribute
namespace MyApp.ServiceInterface
{
public class MyServices : Service
{
[Authenticate]
public object Any(Hello request)
{
return new HelloResponse { Result = $"Hello, {request.Name}!" };
}
to the Any method in MyServices, I'm not able to call /Hello with the token.
/requiresauth gives the same result

I was able to get this to work by adding the following to MyApp.Startup.ConfigureServices()...
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options => {
options.Authority = "https://localhost:5000";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
});

Related

Blazor Server App with Azure AD authentication - Token expired and Custom AuthenticationStateProvider

I have built a Blazor Server App with Azure AD authentication. This server app access a web api written in net core and sends the JWT token to that api. Everything is working, data is gathered, page is displayed accordingly.
The problem is: after some time, when user interacts with some menu option in UI, nothing else is returned from webapi. After some tests I found out that the token has expired, then when it is sent to web api, it is not working. But the AuthenticationState remains same, like it is authenticated and valid irrespective the token is expired.
Thus, I have been trying some suggestions like : Client side Blazor authentication token expired on server side. Actually it is the closest solution I got.
But the problem is that, after implemented a CustomAuthenticationStateProvider class, even after injected it, the default AuthenticationStateProvider of the app remains like ServerAuthenticationStateProvider and not the CustomAuthenticationStateProvider I have implemented. This is part of my code:
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly IConfiguration _configuration;
private readonly ITokenAcquisition _tokenAcquisition;
public CustomAuthenticationStateProvider(IConfiguration configuration, ITokenAcquisition tokenAcquisition)
{
_configuration = configuration;
_tokenAcquisition = tokenAcquisition;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var apiScope = _configuration["DownloadApiStream:Scope"];
var anonymousState = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
string savedToken = string.Empty;
try
{
savedToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { apiScope });
}
catch (MsalUiRequiredException)
{
savedToken = string.Empty;
}
catch (Exception)
{
savedToken = string.Empty;
}
if (string.IsNullOrWhiteSpace(savedToken))
{
return anonymousState;
}
var claims = ParseClaimsFromJwt(savedToken).ToList();
var expiry = claims.Where(claim => claim.Type.Equals("exp")).FirstOrDefault();
if (expiry == null)
return anonymousState;
// The exp field is in Unix time
var datetime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiry.Value));
if (datetime.UtcDateTime <= DateTime.UtcNow)
return anonymousState;
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt")));
}
public void NotifyExpiredToken()
{
var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
var authState = Task.FromResult(new AuthenticationState(anonymousUser));
NotifyAuthenticationStateChanged(authState);
}
private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
var claims = new List<Claim>();
var payload = jwt.Split('.')[1];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);
if (roles != null)
{
if (roles.ToString().Trim().StartsWith("["))
{
var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());
foreach (var parsedRole in parsedRoles)
{
claims.Add(new Claim(ClaimTypes.Role, parsedRole));
}
}
else
{
claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
}
keyValuePairs.Remove(ClaimTypes.Role);
}
claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));
return claims;
}
private byte[] ParseBase64WithoutPadding(string base64)
{
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
}
This is my Program.cs where I added the services :
builder.Services.AddScoped<CustomAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<CustomAuthenticationStateProvider>());
Here in the MainLayou.razor, I inject the service and try to use it :
#inject CustomAuthenticationStateProvider authenticationStateProvider;
protected async override Task OnInitializedAsync()
{
var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User?.Identity == null || !authState.User.Identity.IsAuthenticated)
{
authenticationStateProvider.NotifyExpiredToken();
}
await base.OnInitializedAsync();
}
The problem comes up here, because the authenticationStateProvider is not an instance of the CustomAuthenticationStateProvider , but the instance of ServerAuthenticationStateProvider. It is like AuthenticationStateProvider was not replaced by the custom implementation, therefore I can't use the NotifyAuthenticationStateChanged and inform the CascadingAuthenticationState that it was changed.
If anyone has already been thru this or have any suggestion, it would be appreciated.
Actually I just wanna to change authentication state to not authenticated. So user will be pushed to login again using Azure AD.
Thanks

Azure Mobile App OAuth with API Controller

Using above service with Xamarin form, I have enabled authentication with OAuth (Microsoft and Google) at server level.
Call from Swagger works fine. However I'm getting 401 error accessing this via the app. This neither works for TableController nor APIController. I'm not using EasyTables. Following is my code.
public async Task<bool> AuthenticateAsync()
{
bool success = false;
try
{
if (user == null)
{
user = await ItemManager.DefaultManager.CurrentClient.LoginAsync(this, MobileServiceAuthenticationProvider.MicrosoftAccount);
Constants.MobileToken = user.MobileServiceAuthenticationToken;
}
success = true;
}
catch (Exception ex)
{
CreateAndShowDialog(ex.Message, "Authentication failed");
}
return success;
}
public async Task<ObservableCollection<Item>> GetItemsAsync(bool syncItems = false)
{
try
{
IEnumerable<Item> items = await itemTable
.ToEnumerableAsync();
return new ObservableCollection<Item>(items);
}
catch (MobileServiceInvalidOperationException msioe)
{
Debug.WriteLine(#"Invalid sync operation: {0}", msioe.Message);
}
catch (Exception e)
{
Debug.WriteLine(#"Sync error: {0}", e.Message);
}
return null;
}
I tried using rest service client, but not sure how to pass the authentication header. As I seen by Swagger, its actually sending via cookie AppServiceAuthSession. How should it be done via Xamarin Forms?
public ItemManager(IRestService service)
{
restService = service;
}
public Task<List<Item>> GetTasksAsync()
{
return restService.RefreshDataAsync();
}
I read that the token we must supply as the 'X-ZUMO-AUTH' is not the access token that provider send back to us; it is the token that the mobile service backend sends back. How we suppose to retrieve this token? And I don't see Swagger sending X-Zumo-Auth header.
Following is my Rest Service initialization :
public RestService()
{
client = new HttpClient(new LoggingHandler(true));
client.MaxResponseContentBufferSize = 256000;
client.DefaultRequestHeaders.Add("x-access_type", "offline");
client.DefaultRequestHeaders.Add("x-zumo-auth", Constants.MobileToken);
client.DefaultRequestHeaders.Add("ZUMO-API-VERSION", "2.0.0");
}
public async Task<List<Item>> RefreshDataAsync()
{
Items = new List<Item>();
var uri = new Uri(string.Format(Constants.RestUrl, string.Empty));
try
{
var response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
Items = JsonConvert.DeserializeObject<List<Item>>(content);
}
}
catch (Exception ex)
{
Debug.WriteLine(#" ERROR {0}", ex.Message);
}
return Items;
}
EDIT
After enabling the server logging - Azure service is actually throwing 404 error. And this only happens if I enable the custom authorization on the server.
After debugging the code, I notice following difference between authentication handled by both Mobile App vs Swagger :
Mobile App sets the Authentication Type as Federation, but Swagger is setting it correctly as microsoftaccount
And this makes the ID different as well :
I must not be passing the token correctly here.
So what I figured out so far is that I need to pass the header X-ZUMO-AUTH with the current user token to make it work.
And handle this header in the API code to make retrieve user details
//Try to retrieve from header if available
actionContext.Request.Headers.TryGetValues("x-zumo-auth", out auth_token);
if (auth_token !=null)
{
try
{
string urlPath = string.Concat(new Uri(actionContext.Request.RequestUri, actionContext.Request.GetRequestContext().VirtualPathRoot).AbsoluteUri, ".auth/me");
var result = Get<List<AzureUserDetail>>(HttpWebRequest.Create(urlPath), auth_token.FirstOrDefault(), null)?.FirstOrDefault();
userID = result.User_Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Val;
}
catch
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.NotAcceptable);
}
}

Webapi Delegating Handler Returning 401 Response Gets Overridden by Owin Auth

I've reviewed similar posts with solutions that do not work for me.
I have an MVC 5 site hosted in IIS 7.x that serves a web ui - https://www.example.com. Callers can also access api (Webapi 2.2) endpoints to perform certain functions - https://www.example.com/api/x. Some pages/apis are secured while others are not. The mvc/web ui security is managed by owin middleware configured with UseCookieAuthentication and UseWsFederationAuthentication.
The secured pages in the webui are automatically redirected to an ADFS login screen when the user does not have already have a valid SAML token - as desired.
The secured web apis require a separate JWT token passed in the Auth header.
The Webapi is hosted in the same app pool as MVC. The Webapi does NOT have controllers, instead the webapiconfig has routes that leverage a DelegatingHandler to route/pass through the api calls. The Delegating handler is the one that checks to see if the JWT is included in the Auth header and if so allows it to continue to a different internal webapi that validates the JWT. If the JWT is not present then the DelegatingHandler returns a 401.
The 401 return used to work as it shortcircuited a continuation of the request and therefore bypassed any owin pipeline stuff. However, now when the shortcircuit fires the 401 is not returned. Instead the request continues and gets passes onto the Owin auth which then redirects (302) to the ADFS login. I have no idea why. If I change the response status code to something other than 401 then Owin Auth ignores it.
Please see the code below:
Global.asax.cs
public class Global : HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Apis",
routeTemplate: "api/{*path}",
handler: HttpClientFactory.CreatePipeline
(
innerHandler: new HttpClientHandler(),
handlers: new DelegatingHandler[] { new ApiHandler() }
),
defaults: new { path = RouteParameter.Optional },
constraints: null
);
}
}
ApiHandler.cs
internal class ApiHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
try
{
// get jwt from header
var jwt = GetJWTFromHeader(request.Headers);
if (jwt == null)
{
response.ReasonPhrase = "Token required";
return await Task.FromResult<HttpResponseMessage>(response);
}
else if (!IsValidJWT(jwt))
{
response.ReasonPhrase = "Invalid token";
return await Task.FromResult<HttpResponseMessage>(response);
}
response = await base.SendAsync(request, cancellationToken);
}
catch (Exception ex)
{
// log error
response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
// return result
return response;
}
}
Startup.Auth.cs
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertificate;
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(
new CookieAuthenticationOptions()
{
SlidingExpiration = false
}
);
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = ADFS_REALM,
MetadataAddress = ADFS_METADATA,
UseTokenLifetime = true,
TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true
},
Notifications = new WsFederationAuthenticationNotifications
{
RedirectToIdentityProvider = async r =>
{
// do stuff
},
SecurityTokenValidated = async s =>
{
// if we get here, then UI user has valid saml token
// do stuff
}
}
}
});
}
I appreciate any help. Please let me know if more details are needed!
Looks like you can use:
https://msdn.microsoft.com/en-us/library/system.web.http.owinhttpconfigurationextensions.suppressdefaulthostauthentication(v=vs.118).aspx
config.SuppressDefaultHostAuthentication();
Thanks to Finallz I was able to refine my search and come across an answer - found here. In my case, I don't need any special authentication config since I'm manually inspected the JWT in the apihandler. However, by simply including a map to my api path, it naturally overrides the Owin security:
app.Map("/api", inner =>
{
// nothing to do here since we don't have any concrete controllers to manage special authorization for
// we're using apihandlers to pass api traffic through to next stop
});

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;
}
}
}

Using ServiceStack MiniProfiler to profile all service client calls

Context: I'm writing a service using ServiceStack. This service is calling some other remote services (using the ServiceStack JsonServiceClient).
Requirement: show every call to the remote service as a step in MiniProfiler.
Question: what would be the best way to implement this in a generic way?
The original code in my service looked like the following:
// Registration of the serviceclient in Apphost.cs:
// container.Register<IRestClient>(x => new JsonServiceClient("http://host:8080/"));
var client = ResolveService<IRestClient>();
HelloResponse response;
using (Profiler.Current.Step("RemoteService: Get Hello"))
{
response = client.Get(new Hello { Name = "World!" });
}
// ... do something with response ...
I wanted to get rid of the using (Profiler.Current.Step()) in this part of my code to make it easier to read and write.
// Registration of the serviceclient in Apphost.cs:
// container.Register<IRestClient>(x => new ProfiledRestClient("RemoteService", new JsonServiceClient("http://host:8080/")));
var client = ResolveService<IRestClient>();
HelloResponse response = client.Get(new Hello { Name = "World!" });
// ... do something with response ...
I made a wrapper around the existing client that contains the Profiler.Current.Step() code for every method of the IRestClient interface
mentioning the name of the client, the method and the request(type).
// The implementation of the wrapper:
public class ProfiledRestClient : IRestClient
{
readonly string clientName;
readonly IRestClient wrappedClient;
public ProfiledRestClient(string clientName, IRestClient wrappedClient)
{
this.clientName = clientName;
this.wrappedClient = wrappedClient;
}
public TResponse Get<TResponse>(IReturn<TResponse> request)
{
using (Profiler.Current.Step("{0}: Get {1}".Fmt(clientName, request.GetType().Name)))
{
return wrappedClient.Get(request);
}
}
public TResponse Post<TResponse>(IReturn<TResponse> request)
{
using (Profiler.Current.Step("{0}: Post {1}".Fmt(clientName, request.GetType().Name)))
{
return wrappedClient.Post(request);
}
}
// etc. the same for all other methods of IRestClient interface
}
It is working but it feels a bit dirty. Is there a better way of doing this?
Thank you for your insight.

Resources