Skip to content

Commit

Permalink
feat(auth-handler): Enable primary domain filtering
Browse files Browse the repository at this point in the history
This enables filtering on primary domains (google
calls this `hd` or `hosted domain`). When this
field is set, any JWT or opaque token must
be requested with the corresponding domain
to be valid for auth.
  • Loading branch information
Christoph Bühler committed Dec 7, 2020
1 parent 35273a1 commit cfd2e4c
Show file tree
Hide file tree
Showing 49 changed files with 651 additions and 15 deletions.
17 changes: 10 additions & 7 deletions src/Zitadel/Authentication/AuthenticationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,16 +119,19 @@ public static AuthenticationBuilder AddZitadelAuthenticationHandler(
PropertyBag = new Dictionary<string, object>(),
};

// TODO: Hosted Domain
// if (!string.IsNullOrWhiteSpace(options.HostedDomain))
// {
// o.TokenValidationParameters.PropertyBag.Add("hd", options.HostedDomain);
// }
if (!string.IsNullOrWhiteSpace(zitadelOptions.PrimaryDomain))
{
options.TokenValidationParameters.PropertyBag.Add(
"primaryDomain",
zitadelOptions.PrimaryDomain);
}

options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(new ZitadelJwtTokenValidator());
options.SecurityTokenValidators.Add(new ZitadelJwtTokenValidator(zitadelOptions.PrimaryDomain));
options.SecurityTokenValidators.Add(
new ZitadelOpaqueTokenValidator(zitadelOptions.DiscoveryEndpoint));
new ZitadelOpaqueTokenValidator(
zitadelOptions.DiscoveryEndpoint,
zitadelOptions.PrimaryDomain));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ public class ZitadelHandlerOptions

public string DiscoveryEndpoint { get; set; } = ZitadelDefaults.DiscoveryEndpoint;

public string? HostedDomain { get; set; }
public string? PrimaryDomain { get; set; }
}
}
17 changes: 14 additions & 3 deletions src/Zitadel/Authentication/Validation/ZitadelJwtTokenValidator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Buffers;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
Expand All @@ -10,14 +9,26 @@ namespace Zitadel.Authentication.Validation
{
public class ZitadelJwtTokenValidator : JwtSecurityTokenHandler
{
private readonly string? _primaryDomain;

public ZitadelJwtTokenValidator(string? primaryDomain = null)
{
_primaryDomain = primaryDomain;
}

public override ClaimsPrincipal ValidateToken(
string token,
TokenValidationParameters validationParameters,
out SecurityToken validatedToken)
{
var principal = base.ValidateToken(token, validationParameters, out validatedToken);

// TODO: hosted domain check.
if (_primaryDomain != null && !principal.HasClaim(ZitadelDefaults.PrimaryDomainClaimName, _primaryDomain))
{
// The user-token does not contain a primary domain claim
// or it was the wrong value.
return new ClaimsPrincipal();
}

var roles = principal.FindFirstValue(ZitadelDefaults.RoleClaimName);
if (!string.IsNullOrWhiteSpace(roles))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ namespace Zitadel.Authentication.Validation
public class ZitadelOpaqueTokenValidator : ISecurityTokenValidator
{
private const string AuthorizationHeader = "Authorization";
//TODO: Hosted domain check

private static readonly HttpClient Client = new();

private readonly ConfigurationManager<OpenIdConnectConfiguration> _configuration;

private readonly string? _primaryDomain;
private string? _userInfoEndpoint;
private string? _issuer;

public ZitadelOpaqueTokenValidator(string discoveryEndpoint)
public ZitadelOpaqueTokenValidator(string discoveryEndpoint, string? primaryDomain)
{
_primaryDomain = primaryDomain;
_configuration = new ConfigurationManager<OpenIdConnectConfiguration>(
discoveryEndpoint,
new OpenIdConnectConfigurationRetriever(),
Expand Down Expand Up @@ -66,11 +67,19 @@ public ClaimsPrincipal ValidateToken(
.ReadFromJsonAsync<UserInfo>()
.Result;

if (_primaryDomain != null && userInfo.PrimaryDomain != _primaryDomain)
{
// The user-info does not contain a primary domain claim
// or it was the wrong value.
return new ClaimsPrincipal();
}

var identity = new ClaimsIdentity(
new[]
{
Claim(ClaimTypes.NameIdentifier, userInfo.Id),
Claim("sub", userInfo.Id),
Claim(ZitadelDefaults.PrimaryDomainClaimName, userInfo.PrimaryDomain),

Claim(ClaimTypes.Name, userInfo.Name),
Claim(ClaimTypes.GivenName, userInfo.GivenName),
Expand Down Expand Up @@ -139,7 +148,8 @@ private readonly struct UserInfo
[JsonPropertyName(ZitadelDefaults.RoleClaimName)]
public Dictionary<string, Dictionary<string, string>>? Roles { get; init; }

//TODO: Hosted domain
[JsonPropertyName(ZitadelDefaults.PrimaryDomainClaimName)]
public string? PrimaryDomain { get; init; }
}
}
}
2 changes: 2 additions & 0 deletions src/Zitadel/Authentication/ZitadelDefaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ public static class ZitadelDefaults
public const string DiscoveryEndpoint = "https://issuer.zitadel.ch/.well-known/openid-configuration";

public const string RoleClaimName = "urn:zitadel:iam:org:project:roles";

public const string PrimaryDomainClaimName = "urn:zitadel:iam:org:domain:primary";
}
}
10 changes: 10 additions & 0 deletions tests/Zitadel.Spa.Dev/Controller/AuthorizedApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ public object JwtAuthedGet()
public object BearerAuthedGet()
=> Result();

[HttpGet("hd-jwt")]
[Authorize(AuthenticationSchemes = "ZitadelAuthHandlerJWTHostedDomain")]
public object HdJwtAuthedGet()
=> Result();

[HttpGet("hd-bearer")]
[Authorize(AuthenticationSchemes = "ZitadelAuthHandlerBearerHostedDomain")]
public object HdBearerAuthedGet()
=> Result();

private object Result() => new
{
Ping = "Pong",
Expand Down
16 changes: 15 additions & 1 deletion tests/Zitadel.Spa.Dev/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,23 @@ public void ConfigureServices(IServiceCollection services)
.AddZitadelAuthenticationHandler(
"ZitadelAuthHandlerJWT",
o => o.ClientId = "84891356119558811@zitadel_net")
.AddZitadelAuthenticationHandler(
"ZitadelAuthHandlerJWTHostedDomain",
o =>
{
o.ClientId = "84891356119558811@zitadel_net";
o.PrimaryDomain = "smartive.zitadel.ch";
})
.AddZitadelAuthenticationHandler(
"ZitadelAuthHandlerBearer",
o => o.ClientId = "84891241816386203@zitadel_net");
o => o.ClientId = "84891241816386203@zitadel_net")
.AddZitadelAuthenticationHandler(
"ZitadelAuthHandlerBearerHostedDomain",
o =>
{
o.ClientId = "84891241816386203@zitadel_net";
o.PrimaryDomain = "smartive.zitadel.ch";
});
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
Expand Down
26 changes: 26 additions & 0 deletions tests/Zitadel.Spa.Dev/wwwroot/hd-bearer/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"files": {
"main.css": "/hd-bearer/static/css/main.d3aef3ac.chunk.css",
"main.js": "/hd-bearer/static/js/main.1f84db4e.chunk.js",
"main.js.map": "/hd-bearer/static/js/main.1f84db4e.chunk.js.map",
"runtime-main.js": "/hd-bearer/static/js/runtime-main.db7932c4.js",
"runtime-main.js.map": "/hd-bearer/static/js/runtime-main.db7932c4.js.map",
"static/css/2.37865b2a.chunk.css": "/hd-bearer/static/css/2.37865b2a.chunk.css",
"static/js/2.8ab5b71a.chunk.js": "/hd-bearer/static/js/2.8ab5b71a.chunk.js",
"static/js/2.8ab5b71a.chunk.js.map": "/hd-bearer/static/js/2.8ab5b71a.chunk.js.map",
"index.html": "/hd-bearer/index.html",
"precache-manifest.36bd386d44cf58b390009591b12a6f76.js": "/hd-bearer/precache-manifest.36bd386d44cf58b390009591b12a6f76.js",
"service-worker.js": "/hd-bearer/service-worker.js",
"static/css/2.37865b2a.chunk.css.map": "/hd-bearer/static/css/2.37865b2a.chunk.css.map",
"static/css/main.d3aef3ac.chunk.css.map": "/hd-bearer/static/css/main.d3aef3ac.chunk.css.map",
"static/js/2.8ab5b71a.chunk.js.LICENSE.txt": "/hd-bearer/static/js/2.8ab5b71a.chunk.js.LICENSE.txt",
"static/media/logo.svg": "/hd-bearer/static/media/logo.5d5d9eef.svg"
},
"entrypoints": [
"static/js/runtime-main.db7932c4.js",
"static/css/2.37865b2a.chunk.css",
"static/js/2.8ab5b71a.chunk.js",
"static/css/main.d3aef3ac.chunk.css",
"static/js/main.1f84db4e.chunk.js"
]
}
Binary file not shown.
1 change: 1 addition & 0 deletions tests/Zitadel.Spa.Dev/wwwroot/hd-bearer/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><base href="/hd-bearer/"/><title>React App</title><link href="/hd-bearer/static/css/2.37865b2a.chunk.css" rel="stylesheet"><link href="/hd-bearer/static/css/main.d3aef3ac.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,i,l=r[0],c=r[1],a=r[2],p=0,s=[];p<l.length;p++)i=l[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var c=t[l];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={1:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,function(r){return e[r]}.bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="/hd-bearer/";var l=this["webpackJsonpreact-oidc-client-js"]=this["webpackJsonpreact-oidc-client-js"]||[],c=l.push.bind(l);l.push=r,l=l.slice();for(var a=0;a<l.length;a++)r(l[a]);var f=c;t()}([])</script><script src="/hd-bearer/static/js/2.8ab5b71a.chunk.js"></script><script src="/hd-bearer/static/js/main.1f84db4e.chunk.js"></script></body></html>
15 changes: 15 additions & 0 deletions tests/Zitadel.Spa.Dev/wwwroot/hd-bearer/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
47 changes: 47 additions & 0 deletions tests/Zitadel.Spa.Dev/wwwroot/hd-bearer/oidc-client.min.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
"revision": "ea65d2f7b51efe15d5e29e9dc900e1b9",
"url": "/hd-bearer/index.html"
},
{
"revision": "a78d3d37911f04469b2e",
"url": "/hd-bearer/static/css/2.37865b2a.chunk.css"
},
{
"revision": "2d8207e7c2be01287257",
"url": "/hd-bearer/static/css/main.d3aef3ac.chunk.css"
},
{
"revision": "a78d3d37911f04469b2e",
"url": "/hd-bearer/static/js/2.8ab5b71a.chunk.js"
},
{
"revision": "02af0e61c1ccac6e25bebd46b247a2e4",
"url": "/hd-bearer/static/js/2.8ab5b71a.chunk.js.LICENSE.txt"
},
{
"revision": "2d8207e7c2be01287257",
"url": "/hd-bearer/static/js/main.1f84db4e.chunk.js"
},
{
"revision": "e26a293f273dac16b3a3",
"url": "/hd-bearer/static/js/runtime-main.db7932c4.js"
},
{
"revision": "5d5d9eefa31e5e13a6610d9fa7a283bb",
"url": "/hd-bearer/static/media/logo.5d5d9eef.svg"
}
]);
39 changes: 39 additions & 0 deletions tests/Zitadel.Spa.Dev/wwwroot/hd-bearer/service-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Welcome to your Workbox-powered service worker!
*
* You'll need to register this file in your web app and you should
* disable HTTP caching for this file too.
* See https://goo.gl/nhQhGp
*
* The rest of the code is auto-generated. Please don't update this file
* directly; instead, make changes to your Workbox build configuration
* and re-run your build process.
* See https://goo.gl/2aRDsh
*/

importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");

importScripts(
"/hd-bearer/precache-manifest.36bd386d44cf58b390009591b12a6f76.js"
);

self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});

workbox.core.clientsClaim();

/**
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
* See https://goo.gl/S9QRab
*/
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});

workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/hd-bearer/index.html"), {

blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/],
});
26 changes: 26 additions & 0 deletions tests/Zitadel.Spa.Dev/wwwroot/hd-bearer/signin-callback.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">

<head>
<title>Authentification callback processing..</title>
</head>

<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>

<h1>Authentification callback processing...</h1>

<script src="oidc-client.min.js"></script>
<script>
new Oidc.UserManager({ response_mode: "query" }).signinRedirectCallback().then(function () {
window.location = "index.html";
}).catch(function (e) {
console.error(e);
});
</script>

</body>

</html>
7 changes: 7 additions & 0 deletions tests/Zitadel.Spa.Dev/wwwroot/hd-bearer/silent-renew.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script src="oidc-client.min.js"></script>
<script>
var mgr = new Oidc.UserManager();
mgr.signinSilentCallback().catch(function (error) {
console.error(error);
});
</script>

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

Loading

0 comments on commit cfd2e4c

Please sign in to comment.