Spring 4 WebSockect over STOMP Authentication - spring

I'm developing a multiplayer game based on Spring 4 WebSocket.
my server is stateless so in order to identify players i use tokens.
after struggling for sometime with how to identify players over WebSockets i came up with this solution: on the client player registers like this:
var sockjs = new SockJS("http://mygame/games/", null, {server : token});
this adds the token to the url, I have set up a filter using spring security:
String requestURI = request.getRequestURI();
String[] parts = StringUtils.split(requestURI, "/");
if (parts.length == 4) {
String token = parts[1];
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(Role.ROLE_MULTIPLAYER)));
SecurityContextHolder.getContext().setAuthentication(new PreAuthenticatedAuthenticationToken(token, "MULTIPLAYER", authorities));
}
and it works! in all WebSockets requests i have a Principal set.
However some browsers seems to not support this, in Safari for example the Principal is not set, when debugging the request i see that the URL is correct and the filter works but the Principal is not set. same goes for IE, Chrome and FF works.
I'm using STOMP (https://github.com/jmesnil/stomp-websocket) as a messege protocol.
why is there a different behaviour between the browsers?
is it a Spring or Client issue?

Related

Use external api having oauth2 in spring boot

I need to call an external API from my spring boot project. The external API is using OAuth 2 security authentication using authorization_code. I have the client id and secret key, any suggestion would be great.
Tried using SDK provided by DocuSign but while getting access token facing issue as 400 with message consent required.
The easiest way to do this is do download a "quickstart" from DocuSign and pick Java for your language. This does a lot more than just give you Java code, it also configures everything you need for you to be able to make API calls.
https://developers.docusign.com/docs/esign-rest-api/quickstart/
The specific Java code that does Auth Code Grant authentication can be found here:
https://github.com/docusign/code-examples-java/blob/master/src/main/java/com/docusign/core/controller/GlobalControllerAdvice.java
OAuth2AuthenticationToken oauth = (OAuth2AuthenticationToken) authentication;
OAuth2User oauthUser = oauth.getPrincipal();
OAuth2AuthorizedClient oauthClient = authorizedClientService.loadAuthorizedClient(
oauth.getAuthorizedClientRegistrationId(),
oauthUser.getName()
);
if (oauth.isAuthenticated()) {
user.setName(oauthUser.getAttribute("name"));
if (oauthClient != null){
user.setAccessToken(oauthClient.getAccessToken().getTokenValue());
} else {
user.setAccessToken(((OAuth.OAuthToken) oauthUser.getAttribute("access_token")).getAccessToken());
}
if (account.isEmpty()) {
account = Optional.ofNullable(getDefaultAccountInfo(getOAuthAccounts(oauthUser)));
}
OAuth.Account oauthAccount = account.orElseThrow(() -> new NoSuchElementException(ERROR_ACCOUNT_NOT_FOUND));
session.setAccountId(oauthAccount.getAccountId());
session.setAccountName(oauthAccount.getAccountName());
// TODO set this more efficiently with more APIs as they're added in
String basePath = this.getBaseUrl(apiIndex, oauthAccount) + apiIndex.getBaseUrlSuffix();
session.setBasePath(basePath);
}

How to disconnect a Stomp client Session from Spring

I know how to disconnect Sessions from Client Side, but I couldn't find a way to disconnect my session from my Spring Boot Backend. I've already seen the following post:
Disconnect client session from Spring websocket stomp server
This would kinda adress my problem, but I thought maybe there is a much more elegant or easier way to solve my problem, since the above mentioned post is already 8 years old. And still i couldn't get it to work as expected.
Trying to sketch my exact problem:
JS-Client-Side looks like this(pseudo code):
![creates a simple request and sends it to my Spring Backend]
function subscribeToUser(){
request = {};
request.type = "USER";
var user = {};
user.userId = userId;
user.email = email;
request.user = user;
send(request);
}
Server-Side:
Here I detect a Subscribe Event, extract the destination, and check if it is valid. If there is some problem
with the destination I want my server to disconnect from that client.(This should happen in line 122)
#EventListener
private void handleSessionSubscribe(SessionSubscribeEvent event){
String destination =
event.getMessage().getHeaders().get("simpDestination").toString();
Principal p = canSubscribeToThatEndpoint(destination,
event.getUser());
}
private Principal canSubscribeToThatEndpoint(String dest, Principal
user){
if(dest.containt("some invalid thing")){
//close the session from server-side
}else return user;
}
I already tried to follow the mentioned StackOverflow Post but I couldn't get it to run. Also another method would be to send a message from the backend and trigger a disconnect Event in JS. But I think it would be convient(if there is a way) to access current client sessions in Backend and disconnect from them if needed.

websockets and authentication with identityserver4

I am using .net core 1.1 and identityserver 4 to get tokens and validate users. The web api works fine reading the bearer token from the headers and getting the user principal claims.
Now I want to use a websocket (not SignalR) for sending notifications. I can open a ws:// channel (or wss) but token isn't sent with the headers, so in the .net core application I have no information of the user (User Claims and Identity).
How can I authenticate the user through the websocket? I did a search but couldn't find any helpful information.
Thanks
There are two main problems related to the authentication in WebSocket middleware:
Authorization should be called manually
First of all, authorization is not applied to web socket request (as it is not a controller which can be marked with Authorize attribute).
That's why in WebSocket middleware you need to call authorization by your self. This is easy to achieve by calling AuthenticateAsync extension method of the HttpContext object.
So, your middleware will be look something like this:
public class WebSocketMiddleware
{
private readonly RequestDelegate next;
public WebSocketMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await this.next.Invoke(context);
return;
}
AuthenticateResult authenticateResult =
await context.AuthenticateAsync(OAuthValidationDefaults.AuthenticationScheme);
....
});
}
So, using authentication results you can check if the user is authenticated or not and then access authenticated user information.
Passing bearer token to web sockets request
For Web Socket connections, the default Authorization header does not work, because the WebSockets JS API doesn't allow setting custom parameters. To work around this limitation, the access token is passed quite often in the query string.
To make authentication middleware to use it, you need to update authentication validation options. This basically can be done in your startup script like this:
services
.AddAuthentication()
.AddOAuthValidation(options =>
{
options.Events = new OAuthValidationEvents
{
// Note: for Web Socket connections, the default Authorization header does not work,
// because the WebSockets JS API doesn't allow setting custom parameters.
// To work around this limitation, the access token is retrieved from the query string.
OnRetrieveToken = context =>
{
context.Token = context.Request.Query["access_token"];
return Task.FromResult(0);
}
};
});
The following code can be used as an example to add access token to web socket url during connection initializing:
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
const wsUri = protocol + "//" + window.location.host + "/ws" + "?access_token=" + token;
this.socket = new WebSocket(wsUri);

what protocol to use with ADFS when security webapi for non-browser clients

Our webapi endpoints are used for both browser based clients (angular) and non-browser based clients (restsharp) and the webapi are currently secured using passive WS-Federation as the protocol and ADFS as the STS. We currently use a rather convoluted workaround for the restsharp clients since passive WS-Federation really isn't optimal for non-browser clients so we would like to find a better way to secure our webapi endpoints for these types of clients without having to replace ADFS or add extra infrastructure.
My understanding is that OAuth2 "Resource Owner Password Credentials Grant" (grant_type=password) would support this scenario nicely but unfortunately it is currently not supported by ADFS.
So, my question is this, is there a nice way to use the one OAuth2 flow that ADFS supports, namely "Authorization Code Grant Flow" (grant_type=authorization_code) to support non-browser based clients?
If this is not possible, can I secure WebApi endpoints using WS-Trust and bearer tokens without resorting to using WCF?
It turns out it was possible to use WS-Trust to get a saml 2.0 token and a WebApi to consume it with a little help from Thinktecture IdentityModel. The following does not include claims transformation so if you need to add claims to the Principal, then a little more work is needed.
The owin startup for the webapi service needs to use the following from Thinktecture.IdentityModel.Owin:
app.UseSaml2BearerAuthentication(
audience: new Uri(ConfigurationManager.AppSettings["FederatedSecurity.Realm"]),
issuerThumbprint: ConfigurationManager.AppSettings["FederatedSecurity.Thumbprint"],
issuerName: ConfigurationManager.AppSettings["FederatedSecurity.Authority"]);
For the client to request the saml 2.0 token from ADFS
private static SecurityToken RequestSecurityToken()
{
var trustChannelFactory = new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential), new EndpointAddress(new Uri("https://yourAdfsServer/adfs/services/trust/13/usernamemixed"), new AddressHeader[0]))
{
TrustVersion = TrustVersion.WSTrust13,
Credentials = { UserName = { UserName = #"u$ern#me", Password = "p#ssw0rd" } }
};
var requestSecurityToken = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Bearer,
TokenType = TokenTypes.Saml2TokenProfile11,
AppliesTo = new EndpointReference(_audience)
};
RequestSecurityTokenResponse response;
var securityToken = trustChannelFactory.CreateChannel().Issue(requestSecurityToken, out response);
return securityToken;
}
And for the client to call the service (using HttpClient but RestSharp will also work)
private static void CallService(SecurityToken token)
{
using (HttpClient client = new HttpClient())
{
client.SetBearerToken(Convert.ToBase64String(Encoding.UTF8.GetBytes(token.ToTokenXmlString())));
var httpMessage = client.GetAsync(new Uri(_restEndpoint)).Result;
}
}

How to authenticate programmatically to UAG for SharePoint with Windows Phone app using session cookie

In a Windows Phone application, I'm trying to read SharePoint data that is protected by UAG, and want to support passing a session cookie to UAG.
The white paper, Building Windows Phone 7 applications with SharePoint 2010 Products and Unified Access Gateway (UAG), http://technet.microsoft.com/en-us/library/hh180841.aspx, demonstrates passing user credentials each time to UAG.
But, how do I store and reuse the session cookie that UAG passes back to the client?
//Example from white paper
string url = String.Format(“{0}/my/_layouts/activityfeed.aspx?consolidated=true", AppSettings.Url);
System.Uri authServiceUri = new Uri(url);
HttpWebRequest client = WebRequest.CreateHttp(authServiceUri) as HttpWebRequest;
client.Headers["User-Agent"] = "Microsoft Office Mobile";
client.Headers["Authorization"] = "Basic "
+ Convert.ToBase64String(Encoding.UTF8.GetBytes(AppSettings.Username + ":"
+ AppSettings.Password))+ System.Environment.NewLine;
// Call and handle the response...
This Blog Post, Developing Windows Phone 7 Applications for SharePoint 2010, http://blogs.msdn.com/b/pstubbs/archive/2010/10/04/developing-windows-phone-7-applications-for-sharepoint-2010.aspx, shows how to authenticate with FBA and pass a cookie with request. But I don't know how much of this applies to UAG.
private void Authenticate()
{
System.Uri authServiceUri =new Uri("http://phone.contoso.com/_vti_bin/authentication.asmx");
HttpWebRequest spAuthReq = HttpWebRequest.Create(authServiceUri) as HttpWebRequest;
spAuthReq.CookieContainer = cookieJar;
spAuthReq.Headers["SOAPAction"] = "http://schemas.microsoft.com/sharepoint/soap/Login";
spAuthReq.ContentType = "text/xml; charset=utf-8";
spAuthReq.Method = "POST";
//add the soap message to the request
spAuthReq.BeginGetRequestStream(new AsyncCallback(spAuthReqCallBack), spAuthReq);
}
// After authenticated and cookie is set
ListsService.ListsSoapClient lists = new ListsService.ListsSoapClient();
lists.CookieContainer = cookieJar;
Both approaches will work with UAG in some circumstances. If you use HttpWebRequest, You can authenticate to UAG by setting the basic authorization and useragent headers on each call. You don't have to pass the cookie to UAG. SharePoint will return the data on the next response.
You can also modify the above FBA example so that it works with UAG:
You must add the useragent and basic authentication headers in the Authenticate method.
spAuthReq.Headers["User-Agent"] = "Microsoft Office Mobile";
spAuthReq.Headers["Authorization"] = "Basic " . . .
Couple tips:
Since you'll be using HTTPS, you will also need to change the clientconfig security mode to transport.
Test the Office Hub against your UAG/SharePoint environment before you begin development.
UAG signs cookies, which means they are obfuscated everytime a user logs in. Also UAG doesn't do cookie sign in - it uses them for sessions.

Resources