How to put an Api key in the Authenticate message? - servicestack

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.

Related

Using JwtAuthProviderReader with ServiceStack and AWS Cognito

We are using an existing userpool in AWS Cognito, a separate client app is created for our api server.
When using the hosted UI from Cognito accessToken, idToken and refreshToken.
The issue is when adding JwtAuthProviderReader to AuthFeature for doing the token validation we get "HTTP/1.1 401 Unauthorized" for any endpoint we create with the [Authenticate] attribute.
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[]
{
new JwtAuthProviderReader
{
Audience = "11rqr096c55xxxxxxxxxxxxxx", // App client id
Issuer = "https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_xxXxxXXxX",
HashAlgorithm = "RS256",
PublicKey = new RSAParameters
{
Modulus = Base64UrlEncoder.DecodeBytes("JRDU3q2XoOcKGjcj1DsJ3Xj .... DTNVCGzUCGosKGYL0Q"),
Exponent = Base64UrlEncoder.DecodeBytes("AQAB")
},
RequireSecureConnection = false,
}
}
)
{
IncludeAssignRoleServices = false
});
The modulus and Exponent is from e and n in Well-Known response ref https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_xxXxxXXxX/.well-known/jwks.json
Service protected by Authenticate attribute always returns HTTP/1.1 401 Unauthorized
[Authenticate]
public object Get(GetTenants request)
{
return ...;
}
How can we know that our JwtAuthProviderReader is setup correctly?
You can test whether your JWT can be validated with ServiceStack's JWT Auth Provider by testing the JWT Token in the IsJwtValid API of a configured JwtAuthProviderReader instance, e.g:
var jwtAuth = new JwtAuthProviderReader { ... };
jwtAuth.IsJwtValid(jwt);
This will return false if the JWT is not valid. There's a lot of reasons why a JWT wouldn't be valid, so the first thing I'd check is to test you can actually decrypt the JWE Token by calling GetVerifiedJwePayload(), e.g:
var jsonObj = jwtAuth.GetVerifiedJwePayload(null, jwt.Split('.'));
If successful it will return a decrypted but unverified JSON Object. This will fail with your current configuration because decrypting an RSA JWE Token requires configuring the complete PrivateKey, i.e. not just the PublicKey components.
If you're only using RSA256 to verify the JWT Signature instead of encrypting the JWE Token and jwtAuth.IsJwtValid(jwt) returns false, you can verify if signature is valid by calling GetVerifiedJwtPayload(), e.g:
var jwtBody = jwtAuth.GetVerifiedJwtPayload(null, jwt.Split('.'));
This will return null if the signature verification failed otherwise it will return a JsonObject with the contents of the JWT Body.
You can then validate the jwtBody payload to check if the JWT is valid, e.g:
var invalidErrorMessage = jwtAuth.GetInvalidJwtPayloadError(jwtBody);
var jwtIsValid = invalidErrorMessage == null;
Which returns null if the JWT is valid otherwise a string error message why it's not.

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(),
};

IdentityServer4 - is it possible to use local login form with external provider and no round trip?

I'm trying to use a local login form to authenticate a user credentials against its external provider (Azure Active Directory).
I understand that, per client, you can enable local login. That helps, as when set to true, I'll get the local login form but but I'm still unclear as to how to fire off the middle ware for that external provider. Is there a way to send client credentials to the external provider to receive an ID token? My current code redirects to the Microsoft login; and then back to my identity server, and then the client application. I want the user to login in through identity server but not have them know it's really authenticating against Azure.
Here's my start up:
var schemeName = "Azure-AD";
var dataProtectionProvibder = app.ApplicationServices.GetRequiredService<IDataProtectionProvider>();
var distributedCache = app.ApplicationServices.GetRequiredService<IDistributedCache>();
var dataProtector = dataProtectionProvider.CreateProtector(
typeof(OpenIdConnectMiddleware).FullName,
typeof(string).FullName, schemeName,
"v1");
var dataFormat = new CachedPropertiesDataFormat(distributedCache, dataProtector);
///
/// Azure AD Configuration
///
var clientId = Configuration["AzureActiveDirectory:ClientId"];
var tenantId = Configuration["AzureActiveDirectory:TenantId"];
Redirect = Configuration["AzureActiveDirectory:TenantId"];
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = schemeName,
DisplayName = "Azure-AD",
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
ClientId = clientId,
Authority = $"https://login.microsoftonline.com/{tenantId}",
ResponseType = OpenIdConnectResponseType.IdToken,
StateDataFormat = dataFormat,
});
app.UseIdentity();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
This is the login.
[HttpGet]
public async Task<IActionResult> ExternalLogin(string provider, string returnUrl)
{
var context = this.HttpContext.Authentication;
List<AuthenticationDescription> schemes = context.GetAuthenticationSchemes().ToList();
returnUrl = Url.Action("ExternalLoginCallback", new { returnUrl = returnUrl });
// start challenge and roundtrip the return URL
var props = new AuthenticationProperties
{
RedirectUri = returnUrl,
Items = { { "scheme", provider } }
};
//await HttpContext.Authentication.ChallengeAsync(provider, props);
return new ChallengeResult(provider, props);
}
In my opinion ,we shouldn't directly pass the username/password directly from other Idp to azure AD for authentication as a security implementation .And even Azure AD supports the Resource Owner Password Credentials Grant ,it's only available in native client. I suggest you keep the normal way and don't mix them .

ServiceStack Ws-Security Auth Provider

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

Server events: How to require authorized clients?

I have a ServerEventsClient which gets notified when the server raises an event.
The Server has a working custom CredentialsAuthProvider implementation.
This is the code to start the client (I customize the urls at the server):
int port = 4711;
string baseMessagingUrl = "http://localhost:{0}/messaging".Fmt(port);
string authorizationUrl = "http://localhost:{0}/api/auth/login".Fmt(port);
string channel = "customer/4711";
var client = new ServerEventsClient(baseMessagingUrl, channel);
client.RegisterNamedReceiver<CcuEventReceiver>("ccu");
client.ServiceClient.Post<AuthenticateResponse>(authorizationUrl, new Authenticate
{
provider = CredentialsAuthProvider.Name,
UserName = "fred",
Password = "123",
RememberMe = true,
});
client.Start();
At the server: before sending a message I check the subscription details via
List<Dictionary<string, string>> subscriptionsDetails = _serverEvents.GetSubscriptionsDetails(changeEvent.CustomerId);
They didn't contain the expected authentication details I send during authorization, but that:
"userId": -6
"displayName": user6
"profileUrl": a url to githubusercontent
Same information within the "OnConnect" event (ServerEventsFeature).
How do I ensure that only authenticated clients are able to subscribe to events?
Support for Authenticating with ServerEventsClient has been added in this commit which is available in v4.0.32+ that's now on MyGet.
Authenticating ServerEvents Client
There are new explicit Authenticate and AuthenticateAsync API's which can be used to authenticate the ServerEvents ServiceClient which now shares cookies with the WebRequest that connects to the event stream, so you can now authenticate with:
client.Authenticate(new Authenticate {
provider = CredentialsAuthProvider.Name,
UserName = "fred",
Password = "123",
RememberMe = true,
});
client.Start();
These API's are just extension methods on ServerEventsClient so can be easily extended if you want customized behavior:
public static AuthenticateResponse Authenticate(this ServerEventsClient client,
Authenticate request)
{
return client.ServiceClient.Post(request);
}
Limiting Server Events to Authenticated Clients Only
There's also a new LimitToAuthenticatedUsers option in ServerEventsFeature to limit access to authenticated clients only:
Plugins.Add(new ServerEventsFeature {
LimitToAuthenticatedUsers = true,
});
Which when enabled will return a 401 Unauthorized for non-authenticated clients.

Resources