My Angular 2 app (coded in typescript) has a simple authentication scheme:
User logs in:
Server returns JSON Web Token (JWT) abc123...
On every API call, the app sends the JWT in the Authorization header
Server validates the JWT and grants access
Now I'd like to add websockets. I'm wondering how to authenticate the user there. Since I don't control which headers are sent to the websocket server (WS), I cannot send the JWT.
My idea so far (not yet implemented):
Client opens websocket: let sock = new WebSocket('wss://example.com/channel/');
WS server accepts the handshake without any authentication check. Standard HTTP headers are available at this stage.
Client listens to the open event on the socket. Once the socket is open:
client sends a message with type='auth' payload='JWT_VALUE'
WS server expects 1st message on a socket to be of type auth. Once that is received, server reads the payload, validates JWT_VALUE and sets an isAuthenticated flag
If validation fails, server disconnects the socket
If a client without isAuthenticated sends any other type of message, server disconnects the socket
2 problems: server resources can be taken up by clients who connect but never send the JWT, and a cleaner solution would block the handshake if the client is not authenticated.
Other ideas:
Client could send JWT in the path: new WebSocket('wss://example.com/channel/<JWT>/')
pro: this info is available during the handshake
con: the path doesn't seem to be the "appropriate" place for a JWT. Specifically because intermediate proxies and access logs will save the path; When designing the HTTP API I already made the decision not to include the JWT in the url
Server could read the client's IP + UserAgent and match against a DB record that was created by the HTTP server when the JWT was issued. Server will then guess who is connecting
pro: this info may be available during the handshake (not sure about IP)
con: it seems horribly insecure to "guess" that a client should be associated with a JWT when the client never presented it in the first place. It would mean for instance that someone who spoofs the victim's UA and uses the same network (proxy, public wifi, university intranet...) will be able to impersonate the victim.
How do you authenticate clients on websockets? Assume the user already logged in via HTTP and that the Angular 2 app has a JWT token.
I settled on the following protocol:
1. Client logs into the site and receives an authentication token (JSON Web Token)
GET /auth
{
user: 'maggie',
pwd: 'secret'
}
// response
{ token: '4ad42f...' }
2. Authenticated client requests a websocket connection ticket
GET /ws_ticket
Authorization: Bearer 4ad42f...
// response: single-use ticket (will only pass validation once)
{ ticket: 'd76a55...', expires: 1475406042 }
3. Client opens the websocket, sending the ticket in query param
var socket = new WebSocket('wss://example.com/channel/?ticket=d76a55...');
4. Websocket server (PHP) then validates the ticket before accepting the handshake
/**
* Receives the URL used to connect to websocket. Return true to admit user,
* false to reject the connection
*/
function acceptConnection($url){
$params = parse_str(parse_url($url, PHP_URL_QUERY));
return validateTicket($params['ticket']);
}
/** Returns true if ticket is valid, never-used, and not expired. */
function validateTicket($ticket){/*...*/}
Use djangorestframework-jwt to generated your JWTs, and the following Django-Channels 2 middleware.
The token can be set via the djangorestframework-jwt http APIs, and it will also be sent for WebSocket connections if JWT_AUTH_COOKIE is defined.
settings.py
JWT_AUTH = {
'JWT_AUTH_COOKIE': 'JWT', # the cookie will also be sent on WebSocket connections
}
routing.py:
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
from json_token_auth import JsonTokenAuthMiddlewareStack
from yourapp.consumers import SocketCostumer
application = ProtocolTypeRouter({
"websocket": JsonTokenAuthMiddlewareStack(
URLRouter([
path("socket/", SocketCostumer),
]),
),
})
json_token_auth.py
from http import cookies
from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
class JsonWebTokenAuthenticationFromScope(BaseJSONWebTokenAuthentication):
"""
Extracts the JWT from a channel scope (instead of an http request)
"""
def get_jwt_value(self, scope):
try:
cookie = next(x for x in scope['headers'] if x[0].decode('utf-8') == 'cookie')[1].decode('utf-8')
return cookies.SimpleCookie(cookie)['JWT'].value
except:
return None
class JsonTokenAuthMiddleware(BaseJSONWebTokenAuthentication):
"""
Token authorization middleware for Django Channels 2
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
try:
# Close old database connections to prevent usage of timed out connections
close_old_connections()
user, jwt_value = JsonWebTokenAuthenticationFromScope().authenticate(scope)
scope['user'] = user
except:
scope['user'] = AnonymousUser()
return self.inner(scope)
def JsonTokenAuthMiddlewareStack(inner):
return JsonTokenAuthMiddleware(AuthMiddlewareStack(inner))
Client opens the websocket, sending the UserName and Password in query param
ws://<username>:<password>#<ip-address><path>
Example: new $WebSocket('ws://user:123456#127.0.0.0/util')
Related
I have an admin dashboard react app that's running on admin.localhost.com:3000. I have an api server that's running on www.localhost.com:1337, and we have one react app for all clients, but each client uses it on a subdomain, for example, abc.localhost.com:4200, bcd.localhost.com:4200.
So from my admin dashboard I'm trying to log into abc as a client. The api server, when requested, verifies the admin, sets the cookie in the response for abc.localhost.com:4200 and responds with link for the client https://abc.localhost.com/home. The browser code passes the link to window.open().
However, if I check the browser tab for https:abc.localhost.com:4200, I don't see the cookie, so obviously the authentication fails.
res.cookie('token', token, {
secure: true,
domain: `abc.localhost.com:4200`,
sameSite: 'Lax',
maxAge: moment()
.add(10, 'minutes')
.valueOf(),
})
withCredentials is enabled for the client side agent, and the api server is configured accordingly.
It can't be done, server running on www.localhost.com:1337 can't set cookies for abc.localhost.com:4200. Allowing that would be apocalyptic.
I am using rest API and currently doing the API call using below where my Nginx(proxy) using this as login validation
{URL:http://127.0.0.1:8888/v1/admin Method:GET Body: Headers:map[Authorization:Basic dXNlcjE6dGVzdDE= apikey:user1 content-type:application/json] }
how do I pass the same credentials in gRPC, it seems we can not send as usual headers as the rest call, is there any way to send header details in gRPC? so that my proxy validates the cred and sends to my server
conn, err := grpc.Dial("127.0.0.1:"+gRPCserver_port, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("can not connect with server %v", err)
}
I am using go lang, can you pls help
grpc-go supports two types of credentials:
TransportCredentials: these apply to the whole connection between client and server
PerRPCCredentials: these can be applied on a per-RPC level
If you are interested in passing authentication related headers from client to server, you would most likely use per-RPC credentials. These can be attached on a per-RPC basis using the PerRPCCredentials call option. They can even be applied for the whole connection using WithPerRPCCredentials dial option. Also, note that some per-rpc credentials can only be transferred on a secure channel. If that is the case, then you would have to configure TransportCredentials (other than the insecure credentials that you mention in your example).
Please refer to examples in the grpc-go repository for configuring encryption and authentication. Hope this helps.
We have a web application and enabled direct client channel to communicate with the hosted BOT framework using directline secret.
Link :BOT - Directline webchat
Sample Code:
BotChat.App({
directLine: { secret: Key },
//dynamically retrieve the logged in user info in your mvc View once the user logged in and pass it on
//and pass thoes info to your bot
user: { id: '', email: '' },
bot: { id: 'testBOT' },
resize: 'detect'
}, document.getElementById("divbot"))
Here is my situration:
1) The user successfully logged in to the application and authorized using the individual account
2) How to authenticate the user in the BOT framework. The Directline secret used to authenticate the calling application. Is there any way to authrorize the authenticate the logged in user in the BOT framework securely?
Thank you
also read about getting the secret token from the key and use for communication. But not sure how to accomplish in the javascript.
It seems that you embed web chat in your MVC website, and you do not want to expose Direct Line Secret (which prevent anyone from putting your bot on their website). You can try this approach:
Create a backend service, and make request to generate Direct Line token in that service, which can avoid exposing Direct Line Secret from client side.
On your JavaScript client, you can make Ajax request to that backend service for getting Direct Line token and initiate BotChat with generated token in Ajax success callback function.
Enable CORS in your backend service to allow some origins and prevent another origins request from accessing that backend service and adding your bot in web page.
For secure your backend service, you can implement request Authentication for it.
You can exchange the key for a token that expires. Here is an mvc example: https://github.com/EricDahlvang/TokenBotExample/tree/master/TokenBotExample
string botChatSecret = ConfigurationManager.AppSettings["BotChatSecret"];
var request = new HttpRequestMessage(HttpMethod.Get, "https://webchat.botframework.com/api/tokens");
request.Headers.Add("Authorization", "BOTCONNECTOR " + botChatSecret);
using (HttpResponseMessage response = await new HttpClient().SendAsync(request))
{
string token = await response.Content.ReadAsStringAsync();
Token = token.Replace("\"", "");
}
Scenario:
Both Web application and Web API need to be authenticated and protected from the server side.
Requirement:
Web application is serving the contents for the browser and browser should be calling Web API directly (i.e. Browser to API).
Question:
Is it possible to authenticate both Web APP and the API using tokens?
Any sample code or clear direction would be highly appreciated.
Normally web applications are authenticated using cookies and APIs are authenticated using tokens.There are some sample projects available here but they are either browser to API (SPA token based) or Server side Web App calling API from server to server.
UPDATE 1
App is saving the TokenValidationParameters and used bootstrapContext.Token within the app controller to grab for server to server communication.
As per #dstrockis, I'm trying to grab the id_token from the Web App soon after the end of validation (not within the app contrller).
I'm using SecurityTokenValidated invoker in OpenIdConnectAuthenticationOptions.Notifications within the Startup class. SecurityTokenValidated receives a parameter of type SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> but I'm not sure where to find the id_token within it. Method is below.
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
return new OpenIdConnectAuthenticationOptions
{
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
MetadataAddress = String.Format(aadInstance, tenant, policy),
AuthenticationType = policy,
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = clientId,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
//NEW METHOD INVOKE ************************************
//******************************************************
SecurityTokenValidated = OnSecurityTokenValidated
//******************************************************
},
Scope = "openid",
ResponseType = "id_token",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
SaveSigninToken = true
},
};
}
//NEW METHOD ************************************
private Task OnSecurityTokenValidated(
SecurityTokenValidatedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> arg)
{
//QUESTION ********************************************************
//How to find the just saved id_token using incoming parameter, arg
//*****************************************************************
return Task.FromResult(0);
}
UPDATE 2
Instead of SecurityTokenValidated, I tried AuthorizationCodeReceived and it's not getting called at all. As discussed here, my redirect url does have an ending slash as well.
Any Ideas?
Our ASP.NET OpenID Connect middleware which supports AAD B2C is built to rely on cookie authentication from a browser. It doesn't accept tokens in a header or anything like that for securing web pages. So I'd say if you want to serve HTML from your web app in the classic way, you need to use cookies to authenticate requests to the web app.
You can definitely get & store tokens within the browser and use those to access your web API, even if you use cookies to authenticate to the web app. There's two patterns I'd recommend:
Perform the initial login using the OpenID Connect Middleware, initiating the flow from the server side as described in the samples. Once the flow completes, the middleware will validate the resulting id_token and drop cookies in the browser for future requests. You can instruct the middleware to save the id_token for later use by using the line of code written here. You can then somehow pass that id_token down to your browser, cache it, and use it to make requests to the API.
The other pattern is the inverse. Start by initiating the login from javascript, using the single page app pattern from the B2C documentation. Cache the resulting id_tokens in the browser, and use them to make API calls. But when the login completes, you can send a request to your web app with the id_token in the body, triggering the OpenID Connect middleware to process the request and issue a session cookie. If you want to know the format of that request, I'd recommend inspecting a regular server side OpenID Connect flow.
Found the answer to my own question and adding here for the future reference.
After a successful validation, id_token can be accessed by invoking the SecurityTokenValidated notification. Code sample is below.
private Task OnSecurityTokenValidated(
SecurityTokenValidatedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> arg)
{
//Id Token can be retrieved as below.
//**************************************
var token = arg.ProtocolMessage.IdToken;
return Task.FromResult(0);
}
However, saving this directly into a browser cookie may not be secure.
We have worklight app with app security defined in application-descriptor.xml. We have challenge handler to handle the challenges. In wlCommonInit() function, we call WL.Client.Connect() function which in turns triggers the challenge handler. User can type in user id / password and authenticate successfully. All good till this point.
In challenge handler after successful authenticate we call ChallengeHandler.submitSuccess() method to inform worklight about successfull authentication.
This call should result into WL.client.connect() onSuccess callback function, but instead it makes lot of request to URL ../App/iphone/init and retuns with 401. Eventually after 1-2 minutes it gets HTTP 200 for particular request and then enters into onSuccess().
Any idea why so many requests which result into 401?
Below is code snippet, in main.js...
WL.Client.connect({
onSuccess : callbackOnSuccess,
onFailure : callbackOnFailure
});
in challengeHandler.js..
$('#loginButton').bind('click', function () {
var reqURL = '/j_security_check';
var options = {};
options.parameters = {
j_username : $('#username').val(),
j_password : $('#password').val()
};
options.headers = {};
ChallengeHandler.submitLoginForm(reqURL, options, ChallengeHandler.submitLoginFormCallback);
});
ChallengeHandler.submitLoginFormCallback = function(response) {
WASLTPARealmChallengeHandler.submitSuccess();
};
Theory:
Do you have a single MobileFirst Server or multiple?
If you have only one server, it would be helpful to get network traffic log from a tool such as Wireshark
If you multiple servers, do you then also happen to have a Load Balancer involved?
In order for authentication to successfully pass there will be several requests - the first for triggering the challenge handler and the second to carry the user credentials. These need to reach the same server.
In case the Load Balancer is misconfigured requests may hit different MobileFirst Servers. It does sound like the requests are getting bounced between servers then meaning that a authentication request hits one server but the credentials requests hits another...
So in the case of multiple servers you need to make sure that Sticky Sessions options is enabled in the used Load Balancer