Distributed Node #10
Authentication & authorization
Difference between Authentication and Authorization
Authentication | Authorization |
---|---|
Determines whether users are who they claim to be | Determines what users can and cannot access |
Challenges the user to validate credentials (for example, through passwords, answers to security questions, or facial recognition) | Verifies whether access is allowed through policies and rules |
Usually done before authorization | Usually done after successful authentication |
Generally, transmits info through an ID Token | Generally, transmits info through an Access Token |
Generally governed by the OpenID Connect (OIDC) protocol | Generally governed by the OAuth 2.0 framework |
Example: Employees in a company are required to authenticate through the network before accessing their company email | Example: After an employee successfully authenticates, the system determines what information the employees are allowed to access |
Authentication Strategy in a Microservice Architecture
Authentication & Authorization on each service
The security logic needs to be implemented repeatedly in each microservice. This causes code duplication between the services.
It distracts the development team from focusing on their domain main service.
Hard to maintain and monitor
Different auth flows & security issues
Netflix
Global Authentication & Authorization Service
The authorization check is a business concern. What specific user roles are allowed to do on the service is governed by business rules. Therefore, the authorization concern should not be handled in the global authentication service.
This strategy increases the latency of processing requests
More maintainance
API Gateway Authentication
The authorization concern should not be handled in the global authentication service.
It reduces the latency (call to Authentication service) and ensures the authentication process is consistent across the application.
Stateful vs Stateless authentication
Stateful authentication
the server creates a session for the user after authenticating. The session id is then stored as a cookie in the user's browser and the user session store in the cache or database. When the client tries to access the server with a given session id, the server attempts to load the user session context for the session store, checks if the session is valid, and decides if the client has to access the desired resource or rejects the request.
Stateless authentication
stores the user session on the client-side. A cryptographic algorithm signs the user session to ensure the session data’s integrity and authority.
Each time the client requests a resource from the server, the server is responsible for verifying the token claims sent as a cookie.
Brief Introduction to JSON Web Token (JWT)
A JWT is an open standard (RFC-7519) that defines a mechanism for securely transmitting information between two parties. The JWT token is a signed JSON object that contains a list of claims which allow the receiver to validate the sender's identity.
The purpose of using the JWT token is for a stateless authentication mechanism. Stateless authentication stores the user session on the client-side.
HMAC
hash-based message authentication code
Is a specific type of message authentication code (MAC) involving a cryptographic hash function and a secret cryptographic key.
HMAC_SHA256("key", "The quick brown fox jumps over the lazy dog") = f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8
Why not use just SHA256?
An HMAC is a MAC which is based on a hash function. The basic idea is to concatenate the key and the message, and hash them together. Since it is impossible, given a cryptographic hash, to find out what it is the hash of, knowing the hash (or even a collection of such hashes) does not make it possible to find the key. The basic idea doesn't quite work out, in part because of length extension attacks, so the actual HMAC construction is a little more complicated. For more information, browse the hmac tag on Cryptography Stack Exchange, especially Why is H(k||x) not a secure MAC construction?, Is H(k||length||x) a secure MAC construction? and HMAC vs MAC functions. There are other ways to define a MAC, for example MAC algorithms based on block ciphers such as CMAC.
Our strategy
User service
Nest.js/TS/Etc
Chat service
nodes
Public :443 HTTPS
OAuth 2 provider
OAuth interceptor
HTTP/REST
WS/Socket.IO
Users DB
Users cache
Messages cache
User disabled notification
Private Kubernetes cluster
HTTPS
WS
Broker
Get user details on connection
Forward auth / HMAC signature headers validation
Facebook OAuth flow
?
OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices. This specification and its extensions are being developed within the IETF OAuth Working Group.
But how to integerate Trafik & Facebook ?
Traefik ForwardAuth
The ForwardAuth middleware delegates authentication to an external service. If the service answers with a 2XX code, access is granted, and the original request is performed. Otherwise, the response from the authentication server is returned.
# Forward authentication to example.com
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-auth
spec:
forwardAuth:
address: https://example.com/auth
Configuration Example
K8s config
And the service itself
const http = require('http');
const fetch = require('node-fetch');
const crypto = require('crypto');
const cookieDomain = process.env.COOKIE_DOMAIN || 'lvh.me';
const app_url = process.env.APP_URL || 'https://lvh.me:8125';
const middleware_url = process.env.MIDDLEWARE_URL || 'https://auth.lvh.me:8125';
const client_id = process.env.CLIENT_ID;
const client_secret = process.env.CLIENT_SECRET;
const cookieName = process.env.COOKIE_NAME || 'check';
const port = 8125;
const redirect_uri = `${middleware_url}/auth`;
const authRedurectUrl = `https://www.facebook.com/v4.0/dialog/oauth?client_id=${client_id}&redirect_uri=${redirect_uri}&scope=email,user_friends&response_type=code&auth_type=rerequest&display=popup`;
function shaSignature(text) {
return crypto.createHmac('sha256', client_secret).update(text).digest('base64');
}
function parseGetParams(str = '') {
return str.split('&').reduce((res, pair) => {
const parts = pair.split('=');
res[parts[0]] = parts[1] || true;
return res;
}, {});
}
function validCookies(cookie = '') {
const cookies = cookie.split(/\s*;\s*/);
const check = cookies.find((c) => c.startsWith(cookieName));
if (!check) {
console.log('@ No check cookies', check, 'cookieName=', cookieName, 'cookies', cookies);
return false;
}
const parts = check.slice(cookieName.length + 1).split('^');
if (parts.length !== 2) {
console.log('@ No parts', parts);
return false;
}
if (shaSignature(parts[1]) !== parts[0]) {
console.log('@ SHA is different', parts, shaSignature(parts[1]));
return false;
}
const userData = parts[1].split('|');
if (userData.length !== 4) {
console.log('@ wron user data', userData);
return false;
}
if (Number(userData[3]) < new Date().getTime()) {
console.log('@ bad date ', Number(userData[3]), new Date().getTime());
}
return Number(userData[3]) > new Date().getTime();
}
function fetchUserData(code) {
return fetch(
`https://graph.facebook.com/v4.0/oauth/access_token?client_id=${client_id}&client_secret=${client_secret}&redirect_uri=${redirect_uri}&code=${code}`
)
.then((resp) => {
if (resp.status >= 400) {
throw new Error('Failed to obtain access_token ' + resp.status);
}
return resp.json();
})
.then(({ access_token, expires_in }) => {
return fetch(
`https://graph.facebook.com/me?access_token=${access_token}&fields=id,email,first_name,last_name`
)
.then((resp) => resp.json())
.then((resp) => [resp, access_token, expires_in]);
});
}
http.createServer(function (request, response) {
console.log('--> req', request.url, request.headers.cookie)
if (request.url.startsWith('/auth')) {
const params = parseGetParams(request.url.split('/auth?')[1]);
fetchUserData(params.code)
.then(([data, acess_token, expiration]) => {
const expires = new Date().getTime() + Number(expiration);
const body = `${data.email}|${encodeURIComponent(data.first_name)}|${encodeURIComponent(data.last_name)}|${expires}`;
const hash = shaSignature(body);
const resp = hash + '^' + body;
response
.writeHead(301, {
Location: app_url,
'Set-Cookie': `${cookieName}=${resp}; domain=${cookieDomain}; HttpOnly; Max-Age=${Number(expiration) / 1000};`,
})
.end();
})
.catch((e) => response
.writeHead(500, {
'Content-Length': Buffer.byteLength(e.message),
'Content-Type': 'text/plain',
})
.end(e.message)
);
} else if (validCookies(request.headers.cookie)) {
response.writeHead(200).end();
} else {
response.writeHead(302, { Location: authRedurectUrl }).end();
}
}).listen(port, () => console.log(`Server started on port: ${port}`));
100 lines implementation :)
No Express
No Typescript
No Babel
No Nest
kind: Deployment
apiVersion: apps/v1
metadata:
namespace: default
name: auth-middleware
labels:
app: auth-middleware
spec:
replicas: 1
selector:
matchLabels:
app: auth-middleware
template:
metadata:
labels:
app: auth-middleware
spec:
containers:
- name: auth-middleware
image: localhost:32000/node-auth-middleware
env:
- name: COOKIE_DOMAIN
value: "nwsd.tk"
- name: APP_URL
value: "https://nwsd.tk"
- name: MIDDLEWARE_URL
value: "https://auth.nwsd.tk"
- name: CLIENT_ID
value: "XXX"
- name: CLIENT_SECRET
value: "XXX"
ports:
- name: web
containerPort: 8125
---
apiVersion: v1
kind: Service
metadata:
name: auth-middleware
spec:
ports:
- protocol: TCP
name: web
port: 8125
selector:
app: auth-middleware
Service Deployment
Now the Facebook part
HTTPS is mandatory
Even for the localhost
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: sso
spec:
forwardAuth:
address: http://auth-middleware:8125
authResponseHeaders:
- "X-Forwarded-User"
trustForwardHeader: true
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingressroutetls
namespace: default
spec:
entryPoints:
- websecure
routes:
- match: Host(`nwsd.tk`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: default-sso@kubernetescrd
tls:
certResolver: myresolver
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: traefik-sso
spec:
entryPoints:
- websecure
routes:
- match: Host(`auth.nwsd.tk`)
kind: Rule
services:
- name: auth-middleware
port: 8125
tls:
certResolver: myresolver
Traefik configuration
vvyshko@kb:~/Projects/distributed-node/client-app$ dig A www.nwsd.tk
; <<>> DiG 9.16.1-Ubuntu <<>> A www.nwsd.tk
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21769
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;www.nwsd.tk. IN A
;; ANSWER SECTION:
www.nwsd.tk. 282 IN A 127.0.0.1
;; Query time: 4 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: нд бер 14 18:34:19 EET 2021
;; MSG SIZE rcvd: 56
sudo microk8s kubectl port-forward service/traefik 443:443
And the domain
You can use any DNS service you like.
I am going to use free dot.tk for the demo
sudo microk8s kubectl port-forward service/traefik 443:443
Forward Traefik https to localhost
DNS SPOOFING (DNS CACHE POISONING)
DNS spoofing is a type of attack in which a malicious actor intercepts DNS request and returns the address that leads to its own server instead of the real address. Hackers can use DNS spoofing to launch a man-in-the-middle attack and direct the victim to a bogus site that looks like the real one, or they can simply relay the traffic to the real website and silently steal the information.
DNS hijacking attack types
There are four basic types of DNS redirection:
- Local DNS hijack — attackers install Trojan malware on a user’s computer, and change the local DNS settings to redirect the user to malicious sites.
- Router DNS hijack — many routers have default passwords or firmware vulnerabilities. Attackers can take over a router and overwrite DNS settings, affecting all users connected to that router.
- Man in the middle DNS attacks — attackers intercept communication between a user and a DNS server, and provide different destination IP addresses pointing to malicious sites.
- Rogue DNS Server — attackers can hack a DNS server, and change DNS records to redirect DNS requests to malicious sites.
Thank You!
Distributed Node #10
By Vladimir Vyshko
Distributed Node #10
Authentication & authorization
- 550