ServiceStack Ws-Security Auth Provider - servicestack

I'm trying to figure out how to support ws-security as authentication mechanism in SS.
My goal is to have all DTO handled in json,xml,saop11,saop12(that part has been achieved following the SS documentation) and supporting multiple auth providers including one based on ws-security.
DTOs should not be affected at all by the authentication mechanism.
In case the DTO will be sent using saop12, the soap message will be the the call sample generated by the metadata endpoint(soap envelope + soap body) plus a soap header including the ws-security element for the WS-Security Username Authentication. A dedidcated "soap auth provider" should inspect the message, use the soap header -> security element and perform the authentication.
Along with the soap auth provider, I may have other built-in auth mechanism that may used for json message and/or other formats.
Exists a SS auth provider based on ws-security that I'm not aware of?
Any guidelines, suggestions, thoughts to implement it?
At the present than my solution
//AppHost
Plugins.Add(new AuthFeature(() => new CustomAuthUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(),
new SoapMessageAuthProvider(),
}
));
// required by the SoapMessageAuthProvider to inspect the message body serching for ws-security element
PreRequestFilters.Add((httpReq, httpRes) =>
{
httpReq.UseBufferedStream = false;
});
I based the SoapMessageAuthProvider on the built-in BasicAuthProvider.
Since the SoapMessageAuthProvider requires to inspect the incoming message on each call serching for ws-security element, I implemented IAuthWithRequest
public void PreAuthenticate(IRequest req, IResponse res)
{
//Need to run SessionFeature filter since its not executed before this attribute (Priority -100)
SessionFeature.AddSessionIdToRequestFilter(req, res, null);
var userPass = ExtractSoapMessageUserNameCredentials(req);//req.GetBasicAuthUserAndPassword();
if (userPass != null)
{
var authService = req.TryResolve<AuthenticateService>();
//var response = authService.Post(new Authenticate
//{
// provider = Name,
// UserName = userPass.Value.Key,
// Password = userPass.Value.Value
//});
authService.Request = req;
var session = authService.GetSession(false);
var userName = userPass.Value.Key;
//Add here your custom auth logic (database calls etc)
var userAuth = new UserAuth();
userAuth.Id = 10;
userAuth.UserName = userName;
var holdSessionId = session.Id;
session.PopulateWith(userAuth); //overwrites session.Id
session.Id = holdSessionId;
session.IsAuthenticated = true;
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
session.UserAuthName = userName;
}
}
//called by CustomAuthUserSession.IsAuthorized
// to be reviewed to keep isolated from other providers
public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null)
{
if (request != null)
{
if (!LoginMatchesSession(session, request.UserName))
{
return false;
}
}
return !session.UserAuthId.IsNullOrEmpty();//filled by PreAuthenticate
}
the custom session calls each provider, including the SoapMessageAuthProvider that meanwhile, through the PreAuthenticate method, filled out the session with authenticated user data.
public class CustomAuthUserSession : AuthUserSession
{
public override bool IsAuthorized(string provider)
{
var tokens = ProviderOAuthAccess.FirstOrDefault(x => x.Provider == provider);
return AuthenticateService.GetAuthProvider(provider).IsAuthorizedSafe(this, tokens);
}
...
}
I need to make sure the soap provider will be always invoked for soap message w/ ws-security and the call should not be authenticated by other providers:
- user get authentication through the CustomCredentialsAuthProvider(cookie based)
- user call the service supply json message within the web request that carries the auth cookie
- a further call sends a soap message carrying the same auth cookie: since the message is in soap format and includs the soap header ws-security, the call should be authenticated only using the soap provider using the soap header ws-security within the message.
I understand that a weird scenario, but I'm trying to understand how to accomplish it.
My guess that happends through the ServiceStack.AuthenticateAttribute line 72
matchingOAuthConfigs.OfType<IAuthWithRequest>()
.Each(x => x.PreAuthenticate(req, res));

Related

Error occuriring with call to user authenticated http trigger

I am using Azure Functions v3
I am trying to use Authentication and I have set my function to User level security for its HttpTriggers
The logic below is called on the startup of my function
protected override void SetupAuthentication(
IServiceCollection services, IConfiguration configuration)
{
var tokenOptions = configuration.GetSection("JwtIssuerOptions")
.Get<TokenConfiguration>();
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = tokenOptions.SecurityKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = tokenOptions.Issuer,
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = tokenOptions.Audience,
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
services.Configure<IdentityConfiguration>(configuration.GetSection("IdentityConfiguration"));
services.AddScoped<CustomJwtBearerEvents>();
services
.AddAuthentication(o =>
{
o.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = tokenValidationParameters;
options.EventsType = typeof(CustomJwtBearerEvents);
});
}
When I call the function externally I get the error
No authentication handler is registered for the scheme 'WebJobsAuthLevel'.
The registered schemes are: Bearer. Did you forget to call AddAuthentication().AddSomeAuthHandler?.
What have I missed?
I need to mimic the same convention as web apps
[FunctionName("GetPayments")]
public async Task<List<PaymentDto>> GetPaymentsAsync(
[HttpTrigger(AuthorizationLevel.User, "post", Route = "payments/get-payments")]
HttpRequest req,
ILogger log)
{
var data = await req.ReadAsStringAsync();
//THis is where I have my logic which I only want to be able to access if the user has permissions
}
I have seen the link below
https://damienbod.com/2020/09/24/securing-azure-functions-using-azure-ad-jwt-bearer-token-authentication-for-user-access-tokens/comment-page-1/?unapproved=127819&moderation-hash=3fdd04b596812933c4c32e8e8c8cf26a#comment-127819
It initially looked to be what I need, but I cant work out how to adapt it so that it just uses the identity token validation side
Any help would be appreciated
Paul
Have a look at: https://www.nuget.org/packages/DarkLoop.Azure.Functions.Authorize
The latest version ensures all built-in authentication is in place before you add your own or extend the built-in ones. By the way JTW Bearer is already configured by the Functions runtime.
All you need to do is call in your method
services.AddFunctionsAuthentication();
services.AddFunctionsAuthorization();
and then chain whatever other schemes you need to configure after the AddFunctionsAuthentication() call. The resulting authentication builder is designed to handle modifications to the jwt bearer (AddJwtBearer(options => ...) and will not break telling you the Bearer scheme already exists.
This package also gives you the ability to use the FunctionsAuthorize attribute to handle granular authorization requirements for your HTTP functions. Here is a blog post with details: https://blog.darkloop.com/post/functionauthorize-for-azure-functions-v3

How to put an Api key in the Authenticate message?

I'm trying to combine the api key auth provider with the encrypted messaging plugin.
var client = new JsonServiceClient(home);
client.BearerToken = "somesecret";
works
but i want my apikey to be in the message so i tried
var authResponse = client.Post(new Authenticate
{
provider = ApiKeyAuthProvider.Name,
UserName = "somesecret"
});
This post fails at runtime with a 401 not authenticated.
How do i get this to work?
IAuthWithRequest Auth Providers like the API Key Auth Provider needs to be sent per request with the Authenticated User Session it establishes only lasts for the lifetime of that request. It can't be used with the Authenticate Service to Authenticate the client as your example tried to do, it must be included in each request to an Authenticated Service.
The normal way to call a protected Service with the API Key is to just populate the BearerToken property:
var client = new JsonServiceClient(baseUrl) {
BearerToken = apiKey
};
Which will then let you call your [Authenticate] Service:
var response = client.Get(new Secure { Name = "World" });
Encrypted Messaging Support
Previously you could only embed the User SessionId within an Encrypted Messaging Request but I've just added support for Authenticating Encrypted Messaging Services with a BearerToken in this commit which works similar to populating a SessionId, where you can now populate a BearerToken as used in API Key and JWT Auth Providers by having your Request DTOs implement IHasBearerToken, e.g:
public class Secure : IHasBearerToken
{
public string BearerToken { get; set; }
public string Name { get; set; }
}
This will let you embed the BearerToken when calling the protected Service, e.g:
IEncryptedClient encryptedClient = client.GetEncryptedClient(publicKey);
var response = encryptedClient.Get(new Secure { BearerToken = apiKey, Name = "World" });
Where it will be embedded and encrypted along with all content in the Request DTO.
Alternatively you can also set the BearerToken property on the IEncryptedClient once and it will automatically populate it on all Request DTOs that implement IHasBearerToken, e.g:
encryptedClient.BearerToken = apiKey;
var response = encryptedClient.Get(new Secure { Name = "World" });
The new BearerToken support in Encrypted Messaging is available from v5.1.1 that's now available on MyGet.

How to call from one authorized service to another service that requires authorization

I have an Item Service:
[Authenticate]
public class ItemService : ServiceStack.Service {
//implementation
}
Within the ItemService I am debugging this Get method that has received a valid Token and is able to successfully create session:
public GetItemResponse Get(GetItem request)
{
var session = SessionAs<CustomUserSession>();
var authToks1 = session.GetAuthTokens();//empty
var authToks2 = session.GetAuthTokens(_authServiceConnection);//null
var authService = new JsonServiceClient(_authServiceConnection);
//not authorized
ConvertSessionToTokenResponse attempt1 = authService.Send(new ConvertSessionToToken());
//not authorized
ConvertSessionToTokenResponse attempt2 = authService.Send(new ConvertSessionToToken() { PreserveSession = true });
var accountService = new JsonServiceClient(_accountServiceConnection)
{
BearerToken = "what to do",
RefreshToken = "what to do"
};
return new GetItemResponse();
}
Obviously I am simply trying to call another service, AccountService:
[Authenticate]
public class AccountService : ServiceStack.Service {
//implementation
}
How to include a JWT Refresh token or JWT Bearer token in a request from one authorized service to another service that has the ServiceStack Authenticate attribute.
Note I am using a custom Auth provider, I have a legacy database.
If this another ServiceStack Service with the same Host you should use the Service Gateway as internal requests are executed in process directly which avoid the request filter validation.
var response = Gateway.Send(new MyRequest());
If you're trying to call a remote ServiceStack Service you can get the JWT Token sent with the request with IRequest.GetJwtToken() extension method and forward it to the downstream Service requests:
var accountService = new JsonServiceClient(_accountServiceConnection)
{
BearerToken = Request.GetJwtToken(),
};

How to enable basic authentication without user sessions with ServiceStack?

According ServiceStack github wiki In order to add/enable basic authentication in ServiceStack following lines of code are required:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new BasicAuthProvider(), //Sign-in with Basic Auth
new CredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}));
But how can I add basic authentication without user sessions?
If you want to perform the authentication without using sessions then you can create a simple request filter that performs the basic authentication yourself.
You can then authenticate the credentials either against your own database or repositor, or you can authenticate against the standard ServiceStack repository shown below:
public class MyAuthenticateAttribute : RequestFilterAttribute
{
public override void Execute(IRequest req, IResponse res, object requestDto)
{
// Determine if request has basic authentication
var authorization = req.GetHeader(HttpHeaders.Authorization);
if(!String.IsNullOrEmpty(authorization) && authorization.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
{
// Decode the credentials
var credentials = Encoding.UTF8.GetString(Convert.FromBase64String(authorization.Substring(6))).Split(':');
if(credentials.Length == 2)
{
// Perform authentication checks. You could do so against your own database
// or you may wish to use the ServiceStack authentication repository IUserAuthRepository
// If you want to check against ServiceStacks authentication repository
var repository = HostContext.TryResolve<IUserAuthRepository>();
if(repository == null)
throw new Exception("Authentication Repository is not configured");
// Try authenticate the credentials
IUserAuth user;
if(repository.TryAuthenticate(credentials[0], credentials[1], out user))
{
// Authenticated successfully
// If you need the user details available in your service method
// you can set an item on the request and access it again in your service
// i.e. req.SetItem("user", user);
// In your service: Request.GetItem("user") as IUserAuth
return;
}
}
}
// User requires to authenticate
res.StatusCode = (int)HttpStatusCode.Unauthorized;
res.AddHeader(HttpHeaders.WwwAuthenticate, "basic realm=\"My Secure Service\"");
res.EndRequest();
}
}
So instead of using the [Authenticate] attribute you would use the [MyAuthenticate] attribute.
In your AppHost Configure method do not add the AuthFeature plugin. You do still however need to add the repository, if that's how you choose to authenticate the credentials against.
container.Register<ICacheClient>(new MemoryCacheClient());
var userRep = new InMemoryAuthRepository();
container.Register<IUserAuthRepository>(userRep);
I hope this helps.

How to propage WebSphere security tokens when calling HTTP from EJB

I have an EJB which makes a call to another server in the cell using HTTP (REST api).
At the EJB context the user is already authenticated and authorized, how can I propagate the security tokens to the other server avoiding the need to provide credentials in the request ?
It is possible to obtain WebSphere's Ltpa token from the security subject and pass it as a cookie for the HTTP call:
public static SingleSignonToken getSSOTokenFromSubject(final Subject subject) {
if (subject == null) {
return null;
}
return AccessController.doPrivileged(new PrivilegedAction<SingleSignonToken>() {
public SingleSignonToken run() {
Set<SingleSignonToken> ssoTokens = subject.getPrivateCredentials(SingleSignonToken.class);
for (SingleSignonToken ssoToken : ssoTokens) {
if (ssoToken.getName().equals("LtpaToken")) {
return ssoToken;
}
}
return null;
}
});
}
// Get cookie to add to outgoing HTTP requests
SingleSignonToken ssoToken = getSSOTokenFromSubject(subject);
String ssoTokenStr = null;
if (ssoToken != null) {
byte[] ssoTokenBytes = ssoToken.getBytes();
ssoTokenStr = com.ibm.ws.util.Base64.encode(ssoTokenBytes);
}
String ssoTokenCookie = "LtpaToken2=" + ssoTokenStr;
By adding the ssoTokenCookie to the request cookies there is no need to provider user credentials.
Cookie ltpaCookie = WebSecurityHelper.getSSOCookieFromSSOToken();
Extracts the SSO token from the subject of current thread and builds an SSO cookie out of it for use on downstream web invocations. Basically what the whole code in the post below does. This method is accessible from WAS 8.x I believe.
Following Jar is needed as compile reference:
com.ibm.ws.admin.client-8.5.0.jar
(I'm using WAS 8.5.5.11 for this example)

Resources