Remember when setting up a login page was easy? It seems like nowadays it can take weeks to start a project--creating a signup form, a login form, a password recovery screen, and all the validation in between. And you haven’t even started on security considerations yet. During this presentation, the attendees will be introduced to OpenID Connect and OAuth. They will also learn how to leverage these technologies to create more secure applications. Most importantly, they will learn how to delegate authorization and authentication so they can focus on their real work and forget about all that security stuff.
Presented at South Florida Code Camp '19
20. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
TRADITIONAL APPLICATIONS
▸ Browser requests a login page
21. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
TRADITIONAL APPLICATIONS
▸ Browser requests a login page
22. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
TRADITIONAL APPLICATIONS
▸ Browser requests a login page
23. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
TRADITIONAL APPLICATIONS
▸ Browser requests a login page
▸ Server validates on the database
24. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
TRADITIONAL APPLICATIONS
▸ Browser requests a login page
▸ Server validates on the database
👍
25. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
TRADITIONAL APPLICATIONS
▸ Browser requests a login page
▸ Server validates on the database
▸ It creates a session and sends back
a cookie identifier
👍
27. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
WHAT’S WRONG WITH TRADITIONAL AUTHENTICATION?
▸ Multiple platforms connecting to
your application
28. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
WHAT’S WRONG WITH TRADITIONAL AUTHENTICATION?
▸ Multiple platforms connecting to
your application
▸ Tightly coupled
29. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
WHAT’S WRONG WITH TRADITIONAL AUTHENTICATION?
▸ Multiple platforms connecting to
your application
▸ Tightly coupled
▸ Sharing credentials to connect to a
third party API
30. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
WHAT’S WRONG WITH TRADITIONAL AUTHENTICATION?
▸ Multiple platforms connecting to
your application
▸ Tightly coupled
▸ Sharing credentials to connect to a
third party API
▸ Users have a gazillions accounts
already, which leads to password
reuse and lower conversion rates
56. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
TOKENS
▸ Access Tokens
▸ Gives you access to a
resource
▸ Controls access to your API
▸ Short Lived
▸ Refresh Tokens
▸ Enable you to get a new
access token
▸ Longed lived
▸ Can be revoked
57. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
TOKENS
▸ Refresh Tokens
▸ Enable you to get a new
access token
▸ Longed lived
▸ Can be revoked
58. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
TOKENS
▸ Refresh Tokens
▸ Enable you to get a new
access token
▸ Longed lived
▸ Can be revoked
59. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
TOKENS
▸ WS Federated
▸ SAML
▸ Custom stuff
▸ …
60. I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
TOKENS
▸ WS Federated
▸ SAML
▸ Custom stuff
▸ JWT
61. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
eyJhbGciOiJIUzI1NiIsInR5cC
I6IkpXVCJ9.eyJzdWIiOiIxMj
M0NTY3ODkwIiwibmFtZSI6I
kpvZWwgTG9yZCIsImFkbWl
uIjp0cnVlLCJzY29wZSI6InBv
c3RzOnJlYWQgcG9zdHM6
d3JpdGUifQ.XesR-
pKdlscHfUwoKvHnACqfpe2
ywJ6t1BJKsq9rEcg
62. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
▸ Header
▸ Payload
▸ Signature
eyJhbGciOiJIUzI1NiIsInR5cC
I6IkpXVCJ9.eyJzdWIiOiIxMj
M0NTY3ODkwIiwibmFtZSI6I
kpvZWwgTG9yZCIsImFkbWl
uIjp0cnVlLCJzY29wZSI6InBv
c3RzOnJlYWQgcG9zdHM6
d3JpdGUifQ.XesR-
pKdlscHfUwoKvHnACqfpe2
ywJ6t1BJKsq9rEcg
63. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
▸ Header eyJhbGciOiJIUzI1NiIsInR5cC
I6IkpXVCJ9.eyJzdWIiOiIxMj
M0NTY3ODkwIiwibmFtZSI6I
kpvZWwgTG9yZCIsImFkbWl
uIjp0cnVlLCJzY29wZSI6InBv
c3RzOnJlYWQgcG9zdHM6
d3JpdGUifQ.XesR-
pKdlscHfUwoKvHnACqfpe2
ywJ6t1BJKsq9rEcg
64. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
▸ Header eyJhbGciOiJIUzI1NiIsInR5cC
I6IkpXVCJ9
65. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
▸ Header atob(‘eyJhbGciOiJIUzI1NiIsI
nR5cCI6IkpXVCJ9’);
66. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
▸ Header {
"alg": "HS256",
"typ": "JWT"
}
67. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
▸ Payload eyJhbGciOiJIUzI1NiIsInR5cC
I6IkpXVCJ9.eyJzdWIiOiIxMj
M0NTY3ODkwIiwibmFtZSI6I
kpvZWwgTG9yZCIsImFkbWl
uIjp0cnVlLCJzY29wZSI6InBv
c3RzOnJlYWQgcG9zdHM6
d3JpdGUifQ.XesR-
pKdlscHfUwoKvHnACqfpe2
ywJ6t1BJKsq9rEcg
68. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
▸ Payload eyJzdWIiOiIxMjM0NTY3OD
kwIiwibmFtZSI6IkpvZWwgT
G9yZCIsImFkbWluIjp0cnVlL
CJzY29wZSI6InBvc3RzOnJl
YWQgcG9zdHM6d3JpdGUi
fQ
69. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
▸ Payload atob(‘eyJzdWIiOiIxMjM0NT
Y3ODkwIiwibmFtZSI6IkpvZ
WwgTG9yZCIsImFkbWluIjp
0cnVlLCJzY29wZSI6InBvc3R
zOnJlYWQgcG9zdHM6d3J
pdGUifQ’);
70. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
▸ Payload {
"sub": "1234567890",
"name": "Joel Lord",
"admin": true,
"scope": "posts:read
posts:write"
}
71. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
▸ Signature XesR-
pKdlscHfUwoKvHnACqfpe2
ywJ6t1BJKsq9rEcg
72. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
▸ Signature hmacsha256(
`${header}.${payload}`,
SECRET_KEY
);
73. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
JSON WEB TOKENS (JWTS, NOT JOTS)
Image: https://jwt.io
75. Auth Server API
var express = require('express');
var Webtask = require('webtask-tools');
var bodyParser = require('body-parser');
var randopeep = require("randopeep");
var jwt = require("jsonwebtoken");
var app = express();
var users = [
{id: 1, username: "joellord", password: "joellord"},
{id: 2, username: "guest", password: "guest"}
];
app.use(bodyParser.json());
app.post("/login", function(req, res) {
if (!req.body.username || !req.body.password) return
res.status(400).send("Need username and password");
var user = users.find(function(u) {
return u.username === req.body.username && u.password ===
req.body.password;
});
if (!user) return res.status(401).send("User not found");
var token = jwt.sign({
sub: user.id,
scope: "api:read",
username: user.username
}, "mysupersecret", {expiresIn: "10 minutes"});
var express = require('express');
var Webtask = require('webtask-tools');
var bodyParser = require('body-parser');
var jwt = require("jsonwebtoken");
var app = express();
var users = [
{id: 1, username: "joellord", password: "joellord"},
{id: 2, username: "guest", password: "guest"}
];
app.use(bodyParser.urlencoded());
app.get("/login", function(req, res) {
var loginForm = "<form method='post'><input type=hidden
name=callback value='" + req.query.callback + "'><input type=text
name=username /><input type=text name=password /><input
type=submit></form>";
res.status(200).send(loginForm);
});
app.post("/login", function(req, res) {
if (!req.body.username || !req.body.password) return
res.status(400).send("Need username and password");
var user = users.find(function(u) {
return u.username === req.body.username && u.password ===
req.body.password;
});
if (!user) return res.status(401).send("User not found");
76. @joel__lord
#sflcc
Auth Server
var express = require('express');
var bodyParser = require('body-parser');
var jwt = require("jsonwebtoken");
var app = express();
// ...
77. @joel__lord
#sflcc
Auth Server
var express = require('express');
var bodyParser = require('body-parser');
var jwt = require("jsonwebtoken");
var app = express();
// ...
78. @joel__lord
#sflcc
Auth Server
var express = require('express');
var bodyParser = require('body-parser');
var jwt = require("jsonwebtoken");
var app = express();
// ...
79. @joel__lord
#sflcc
Auth Server
var express = require('express');
var bodyParser = require('body-parser');
var jwt = require("jsonwebtoken");
var app = express();
// ...
81. @joel__lord
#sflcc
Auth Server
// Requires ...
var users = [...];
app.use(bodyParser.urlencoded());
app.post("/login", function(req, res) {
// POST for login
});
app.get('*', function (req, res) {
res.sendStatus(404);
});
82. @joel__lord
#sflcc
Auth Server
// Requires ...
var users = [...];
app.use(bodyParser.urlencoded());
app.post("/login", function(req, res) {
// POST for login
});
app.get('*', function (req, res) {
res.sendStatus(404);
});
83. @joel__lord
#sflcc
Auth Server
app.post("/login", function(req, res) {
// POST for login
if (!req.body.username || !req.body.password)
return res.status(400).send("Need username and password");
var user = users.find(function(u) {
return u.username === req.body.username && u.password === req.body.password;
});
if (!user) return res.status(401).send("User not found");
var token = jwt.sign({
sub: user.id,
scope: "api:read",
username: user.username
}, "mysupersecret", {expiresIn: "10 minutes"});
res.redirect(req.body.callback + "#access_token=" + token);
});
84. @joel__lord
#sflcc
Auth Server
app.post("/login", function(req, res) {
// POST for login
if (!req.body.username || !req.body.password)
return res.status(400).send("Need username and password");
var user = users.find(function(u) {
return u.username === req.body.username && u.password === req.body.password;
});
if (!user) return res.status(401).send("User not found");
var token = jwt.sign({
sub: user.id,
scope: "api:read",
username: user.username
}, "mysupersecret", {expiresIn: "10 minutes"});
res.redirect(req.body.callback + "#access_token=" + token);
});
85. @joel__lord
#sflcc
Auth Server
app.post("/login", function(req, res) {
// POST for login
if (!req.body.username || !req.body.password)
return res.status(400).send("Need username and password");
var user = users.find(function(u) {
return u.username === req.body.username && u.password === req.body.password;
});
if (!user) return res.status(401).send("User not found");
var token = jwt.sign({
sub: user.id,
scope: "api:read",
username: user.username
}, "mysupersecret", {expiresIn: "10 minutes"});
res.redirect(req.body.callback + "#access_token=" + token);
});
86. @joel__lord
#sflcc
Auth Server
app.post("/login", function(req, res) {
// POST for login
if (!req.body.username || !req.body.password)
return res.status(400).send("Need username and password");
var user = users.find(function(u) {
return u.username === req.body.username && u.password === req.body.password;
});
if (!user) return res.status(401).send("User not found");
var token = jwt.sign({
sub: user.id,
scope: "api:read",
username: user.username
}, "mysupersecret", {expiresIn: "10 minutes"});
res.redirect(req.body.callback + "#access_token=" + token);
});
87. @joel__lord
#sflcc
Auth Server
// Requires ...
var users = [...];
app.use(bodyParser.urlencoded());
app.post("/login", function(req, res) {
// POST for login
});
app.get('*', function (req, res) {
res.sendStatus(404);
});
app.listen(8080, () => console.log("Auth server running on
8080"));}
88. @joel__lord
#sflcc
API
var express = require('express');
var bodyParser = require('body-parser');
var randopeep = require("randopeep");
var expressjwt = require("express-jwt");
var app = express();
89. @joel__lord
#sflcc
API
var express = require('express');
var bodyParser = require('body-parser');
var randopeep = require("randopeep");
var expressjwt = require("express-jwt");
var app = express();
90. @joel__lord
#sflcc
API
var express = require('express');
var bodyParser = require('body-parser');
var randopeep = require("randopeep");
var expressjwt = require("express-jwt");
var app = express();
91. @joel__lord
#sflcc
API
var express = require('express');
var bodyParser = require('body-parser');
var randopeep = require("randopeep");
var expressjwt = require("express-jwt");
var app = express();
92. @joel__lord
#sflcc
API
var express = require('express');
var bodyParser = require('body-parser');
var randopeep = require("randopeep");
var expressjwt = require("express-jwt");
var app = express();
110. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
OPEN ID CONNECT
▸ Built on top of OAuth 2.0
▸ OpenID Connect is to OpenId what JavaScript is to Java
▸ Provides Identity Tokens in JWT format
▸ Uses a /userinfo endpoint to provide this info
111. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
OPEN ID CONNECT
▸ Uses the “scope” to fetch info
▸ openid
▸ Profile
▸ email
▸ address
▸ phone
123. @joel__lord
#sflcc
I DON’T CARE ABOUT SECURITY (AND NEITHER SHOULD YOU)
RESOURCES
▸ General JWT resource
▸ https://jwt.io/
▸ Learn how OIDC works
▸ https://openidconnect.net/
▸ Code samples
▸ http://bit.ly/idontcareaboutsecurity