I'm new to web sockets (specifically to socketIO interfaces, both client and server), and I wonder how basic JWT auth is usually implemented. How the headers are passed through. Should I add them to every socket listener or emit event? Or just once during the connection. Maybe the connection is already considered private because of the connection id?
I am using Socket.io-client v.4 and flask-socketio.
I am interested in general practices and would be grateful for any information or your own experience.
There are many ways to pass authentication when you connect:
HTTP header
Cookie
query string
auth option
To pass a token in a header (not valid when connecting directly via WebSocket):
const socket = io({
extraHeaders: {
"Header-Name": "abcd"
}
});
Same site cookies are always passed to the server. To pass cookies cross-site:
const socket = io("https://my-backend.com", {
withCredentials: true
});
To pass it in the query string:
const socket = io({
query: {
token: 'abcd'
}
});
To pass it with the auth option (Socket.IO v3 and up only):
const socket = io({
auth: {
token: "abcd"
}
});
Related
I am trying to figure out if following is secure and correct way of handling user authentication with websockets when using socket.io. My understanding is that we only need to verify authentication (jwt token) during socket connection and can assume that all subsequent events are authenticated?
I essentially made this middleware to verify jwt token once during connect event and then set user details on socket.
socketIo.use((socket, next) => {
socket.on('connect', async () => {
try {
const { authToken } = socket.handshake.auth
const user = await verifyUserToken(authToken)
socket.user = {
uid: user.uid
}
next()
} catch {
next(new Error(SocketError.UNAUTHORIZED))
}
})
})
Is this correct and secure, or do I need to verify token on every event call?
I have the following KeyCloak Client config, to use pkce authentication flow:
Realm: REALM
Client ID: pkce-client
Client Protocol: openid-connect
Access Type: public
Standard Flow Enabled: ON
Valid Redirect URIs: http://localhost:4200/
Advanced Settings:
Proof Key for Code Exchange Code Challenge Method: S256
I try to authenticate in a flutter App with iOS Simulator via openid_client
https://pub.dev/packages/openid_client like this
authenticate() async {
var uri = Uri.parse('http://$localhost:8180/auth/realms/REALM');
var clientId = 'pkce-client';
var scopes = List<String>.of(['profile', 'openid']);
var port = 4200;
var redirectUri = Uri.parse(http://localhost:4200/);
var issuer = await Issuer.discover(uri);
var client = new Client(issuer, clientId);
urlLauncher(String url) async {
if (await canLaunch(url)) {
await launch(url, forceWebView: true);
} else {
throw 'Could not launch $url';
}
}
var authenticator = new Authenticator(
client,
scopes: scopes,
port: port,
urlLancher: urlLauncher,
redirectUri: redirectUri,
);
var auth = await authenticator.authorize();
var token= await auth.getTokenResponse();
return token;
}
But it only gives me this web view:
The Terminal where KeyCloak is running gives me the following lines:
INFO [org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker] (default task-34) PKCE enforced Client without code challenge method.
WARN [org.keycloak.events] (default task-34) type=LOGIN_ERROR, realmId=REALM, clientId=pkce-client, userId=null, ipAddress=127.0.0.1, error=invalid_request, response_type=code, redirect_uri=http://localhost:4200/, response_mode=query
When using Postman it worked and it provided me the Login page:
But I have additional parameters there, which I do not know where to add them in Authenticator(..) when using openid_client, state is automatically added.
state: <state>
code_challenge: <code-challenge>
code_challenge_method: S256
Do I need to add these code_challenge parameters somewhere in the openid_client method?
Or do I need to change the redirect URL when using an App? I tried with the package_name like proposed here (https://githubmemory.com/repo/appsup-dart/openid_client/issues/32), but it did not work either.
See the source code:
: flow = redirectUri == null
? Flow.authorizationCodeWithPKCE(client)
: Flow.authorizationCode(client)
You have specified redirectUri, so authorizationCode flow was used. But you want authorizationCodeWithPKCE flow in this case. So just make sure redirectUri is null and correct PKCE flow (with correct url parameters, e.g. code_challenge) will be used.
You need to send the redirectUri:null and set the port to 3000 ( you can use your port ).
after that you need to add the redirect uri in keycloak like this http://localhost:3000/ .it will do the trick
(what happening is when you send the redirect uri as null value, open_id client use the pkce flow and use the default url)
I am using JWT token based authentication system. i.e djangorestframework-simplejwt in my backend
Now I am using reactj and axios as frontend:
After providing username and pass to the login api, I got access_token and refresh_token which I stored in the localstorage
Now I am trying to connect to an api using access_token.
I get Token invalid or expired
Example I am trying to change password using this api and provide access_token
const url = "dj-rest-auth/password/change/";
const auth = {
headers: {
Authorization: "Bearer " + localStorage.getItem("access_token"),
Accept: "application/json",
"Content-Type": "application/json",
},
};
const data = {
old_password: old_password,
new_password1: new_password1,
new_password2: new_password2,
};
const promise = axios.post(url, data, auth);
promise
.then((res) => {
console.log(res)
})
.catch((err) => {
if (err.response) {
console.log(`${err.response.status} :: ${err.response.statusText}`)
console.log(err.response.data)
}
})
I can do another api call using refresh_token to get access_token when i get an err.
But sometimes, the err can be due to network error or something else. Then even i try to get access_token using refresh_token, it will just get into a loop.
HOw to do this the right way
If you are using Django as the backend, I would suggest using dj-rest-auth for JWT token authentication. dj-rest-auth requires "djangorestframework-simplejwt" for token management.
It is recommended to store access token and refresh token in httponly cookie so that it is not accessed by javascript.
Add JWTtokenAuthentication as authentication classes in settings.py.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
]
}
Add the below configuration too in settings.py
REST_SESSION_LOGIN = False
SITE_ID=1
REST_USE_JWT = True
JWT_AUTH_COOKIE = 'access-token' #any name
JWT_AUTH_REFRESH_COOKIE = 'refresh_token' #any name
JWT_AUTH_SECURE = True
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
There is an open issue with dj-rest-auth, that requires the below code to be implemented in your back-end Github issue: https://github.com/iMerica/dj-rest-auth/issues/97. As workaround suggested, you have to create a file middleware.py and paste below code.
import json
from django.utils.deprecation import MiddlewareMixin
from yourapp.settings import JWT_AUTH_REFRESH_COOKIE # from settings.py
class MoveJWTRefreshCookieIntoTheBody(MiddlewareMixin):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_view(self, request, view_func, *view_args, **view_kwargs):
if request.path == '/token/refresh/' and JWT_AUTH_REFRESH_COOKIE in request.COOKIES:
if request.body != b'':
data = json.loads(request.body)
data['refresh'] = request.COOKIES[JWT_AUTH_REFRESH_COOKIE]
request._body = json.dumps(data).encode('utf-8')
else:
print("The incoming request body must be set to an empty object.")
return None
By now, your back-end will be successfully generating access token and refresh token. Even your back-end will be capable of refreshing access token using refresh token.
Front-End:
By default, access token and refresh tokens are stored in httponly cookie, so you don't need to worry about that part.
Axios can be used to make call to login-end point to get tokens. Make sure you use "withCredentials" and "Headers" in your request.
Response will be tokens, by default it will be stored in httponly cookie, since we are using dj-rest-auth. For all the consecutive requests, httponly cookie will be included, if tokens are valid, user will be provided access. IF token is expired, you need to make call to refresh endpoint to get new access token.
Since you are in development mode, you have to have same domain for both BE and FE, different ports.You can start django-server using below command and make sure your FE is also running in localhost
python manage.py runserver localhost:8080
dj-rest-auth : https://dj-rest-auth.readthedocs.io/en/latest/index.html
I have a server application running in node.js/Mongoose/MongoDB with a REST interface.
My client application is built in Embarcadero C++Builder/Firemonkey(FMX) and so far all is good with interacting with the node server using the embarcadero REST features (TRESTClient/TRESTRequest/TRESTResponse).
I recently added authentication to my server using JSON Web tokens and the user registration/login is working successfully, giving me back a bearer token using the following code:
const token = jwt.sign({sub: user.id}, process.env.JWT_SECRET, {expiresIn: '30d' })
Accessing data is implemented via express-jwt by sending a REST request with the bearer token. Postman makes it easy to send a request for data using a Bearer token (https://learning.postman.com/docs/sending-requests/authorization/#bearer-token), however I cannot find out how to do this seemingly simple task using Embarcadero's REST features.
I have tried using the Embarcadero REST OAUTH/OAUTH2/SIMPLE/BASIC authentication methods with the bearer token in the Access-Token and Request-Token fields and nothing seems to work.
How can this be done? I am sure this is something simple I am missing but there is next to no documentation I can find.
I figured out an answer for anyone else who is having trouble using authentication in C++Builder with REST:
Design-time method:
--> Setup TRESTClient, TRESTRequest, TRESTResponse
--> In TRESTRequest Params, create a new param with fields:
Name: Authorization, Value: Bearer XXXXXXXX (JWT String), Options: poDoNotEncode (this is the important part
Creating the REST client for authorization at runtime:
// initialize REST client
TRESTClient* pRESTClient = new TRESTClient(BASE_URL);
pRESTClient->ContentType = "application/json";
// connect REST request for querying server
TRESTRequest* pRESTRequest = new TRESTRequest(NULL);
pRESTRequest->Client = pRESTClient;
// connect REST response for receiving JSON from server
TRESTResponse* pRESTResponse = new TRESTResponse(NULL);
pRESTRequest->Response = pRESTResponse;
pRESTResponse->ContentType = "text/html";
// do authenticated query
pRESTRequest->Method = rmGET;
pRESTRequest->Resource = ROUTE_ITEMS;
pRESTRequest->ResourceSuffix = SUBROUTE_ITEMSUFFIX;
pRESTRequest->Params->Clear();
TRESTRequestParameter* param = pRESTRequest->Params->AddItem();
param->Name = "Authorization";
param->ContentType = ctNone;
param->Kind = pkHTTPHEADER;
param->Options << poDoNotEncode;
char temp[512];
sprintf(temp, "Bearer %s", JWT_TOKEN);
param->Value = (const char*)temp;
pRESTRequest->Execute();
The server response is then added to the TRESTResponse->Content field as JSON.
As a note, it is important to have the server configured with express-JWT (https://www.npmjs.com/package/express-jwt) for this to work properly with the following code managing the server (node.js):
app.use(jwt({
secret: process.env.JWT_SECRET,
credentialsRequired: false,
getToken: function fromHeaderOrQuerystring (req) {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1];
} else if (req.query && req.query.token) {
return req.query.token;
}
return null;
}
}));
As per the passportjs documentation, the verifier function is supposed to be called.
But I am not seeing this call back.
I am testing this with okta. In okta the app is configured to send the saml response to /fromokta
The okta is redirecting to /fromokta
What am I missing?
const saml = require('passport-saml').Strategy;
var strategy = new saml({
entryPoint: "<some oktka entiry point>",
issuer: "http://www.okta.com/foobar",
protocol: "http://"
},(profile, done) => {
console.log(">>>> callback from okta");
console.log(profile);
done(profile)
})
app.post('/login', function(req, res) {
passport.authenticate(
'saml',
{
//successRedirect: '/success',
failureRedirect: '/'
}
)}
)
app.post('/fromokta', function(req, resp){
// this gets called
})
This most likely means your POST /login route never gets the request. Look at the network requests and see if there is indeed a POST /login to the URL, make sure the protocol is the same too (http and https)