Authentication & 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 |
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
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
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 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.
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.
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
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.
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
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.
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
K8s config
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
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
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
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 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.
There are four basic types of DNS redirection: