Skip to content

Commit 40934aa

Browse files
authored
OIDC logout (helidon-io#3456)
* Support for OIDC logout when using cookies. * Refactored OidcConfig as it was too big. * Only require encryption for the id token when it is expected to be used. Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
1 parent f34ada8 commit 40934aa

32 files changed

Lines changed: 3502 additions & 361 deletions

File tree

common/http/src/main/java/io/helidon/common/http/SetCookie.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public class SetCookie {
4040
private final String path;
4141
private final boolean secure;
4242
private final boolean httpOnly;
43+
private final SameSite sameSite;
4344

4445
private SetCookie(Builder builder) {
4546
this.name = builder.name;
@@ -50,6 +51,7 @@ private SetCookie(Builder builder) {
5051
this.path = builder.path;
5152
this.secure = builder.secure;
5253
this.httpOnly = builder.httpOnly;
54+
this.sameSite = builder.sameSite;
5355
}
5456

5557
/**
@@ -192,6 +194,11 @@ public String toString() {
192194
result.append(PARAM_SEPARATOR);
193195
result.append("HttpOnly");
194196
}
197+
if (sameSite != null) {
198+
result.append(PARAM_SEPARATOR);
199+
result.append("SameSite=");
200+
result.append(sameSite.text());
201+
}
195202
return result.toString();
196203
}
197204

@@ -207,6 +214,7 @@ public static final class Builder implements io.helidon.common.Builder<SetCookie
207214
private String path;
208215
private boolean secure = false;
209216
private boolean httpOnly = false;
217+
private SameSite sameSite;
210218

211219
private Builder(String name, String value) {
212220
Objects.requireNonNull(name, "Parameter 'name' is null!");
@@ -317,5 +325,55 @@ public Builder httpOnly(boolean httpOnly) {
317325
this.httpOnly = httpOnly;
318326
return this;
319327
}
328+
329+
/**
330+
* The {@code SameSite} cookie parameter.
331+
*
332+
* @param sameSite same site type to use
333+
* @return updated builder
334+
*/
335+
public Builder sameSite(SameSite sameSite) {
336+
this.sameSite = sameSite;
337+
return this;
338+
}
339+
}
340+
341+
/**
342+
* The SameSite attribute of the Set-Cookie HTTP response header allows you to declare if your cookie should be restricted
343+
* to a first-party or same-site context.
344+
*/
345+
public enum SameSite {
346+
/**
347+
* Cookies are not sent on normal cross-site subrequests (for example to load images or frames into a third party site)
348+
* , but are sent when a user is navigating to the origin site (i.e., when following a link).
349+
*
350+
* This is the default cookie value if SameSite has not been explicitly specified in recent browser versions
351+
*/
352+
LAX("Lax"),
353+
/**
354+
* Cookies will only be sent in a first-party context and not be sent along with requests initiated by third party
355+
* websites.
356+
*/
357+
STRICT("Strict"),
358+
/**
359+
* Cookies will be sent in all contexts, i.e. in responses to both first-party and cross-origin requests. If
360+
* SameSite=None is set, the cookie Secure attribute must also be set (or the cookie will be blocked).
361+
*/
362+
NONE("None");
363+
364+
private final String text;
365+
366+
SameSite(String text) {
367+
this.text = text;
368+
}
369+
370+
/**
371+
* Text to write to the same site cookie param.
372+
*
373+
* @return text to send in cookie
374+
*/
375+
public String text() {
376+
return text;
377+
}
320378
}
321379
}

docs-internal/oidc.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
OIDC
2+
----
3+
4+
New Open ID Connect features in Helidon.
5+
6+
# OIDC Logout
7+
The capability to logout requires two pieces of information:
8+
1. The token (JWT) that we usually have in a cookie or in a header
9+
2. The ID token, that we get when obtaining JWT using the code flow
10+
11+
As we need both, we need to store both of these tokens in a cookie (or get them from a header).
12+
This also requires encrypting these tokens, as the ID token is not public information.
13+
14+
To achieve this, we need
15+
16+
1. either configuration of encryption as part of OIDC configuration,
17+
or use `Security` instance registered in global context (and named encryption/decryption configured).
18+
2. support for encrypted JWT and capability to encrypt existing JWT ourselves
19+

examples/security/idcs-login/README.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ Edit application.yaml for IdcsMain.java or OidcConfig variable definition for Id
1717
1. Create two resources called `first_scope` and `second_scope`
1818
2. Primary Audience = `http://localhost:7987/"` (ensure there is a trailing /)
1919
3. Within **Client Configuration**
20-
1. Register a client
21-
2. Allowed Grant Types = Client Credentials,JWT Assertion, Refresh Token, Authorization Code
22-
3. Check "Allow non-HTTPS URLs"
23-
4. Set ReDirect URL to `http://localhost:7987/oidc/redirect`
24-
5. Client Type = Confidential
25-
6. Add all Scopes defined in the resources section
20+
1. Register a client
21+
2. Allowed Grant Types = Client Credentials,JWT Assertion, Refresh Token, Authorization Code
22+
3. Check "Allow non-HTTPS URLs"
23+
4. Set Redirect URL to `http://localhost:7987/oidc/redirect`
24+
5. Client Type = Confidential
25+
6. Add all Scopes defined in the resources section
26+
7. Set allowed operations to `Introspect`
27+
8. Set Post Logout Redirect URL to `http://localhost:7987/loggedout`
2628

2729
Ensure you save and *activate* the application
2830

@@ -48,10 +50,7 @@ Try the endpoints:
4850

4951
3. Open http://localhost:7987/rest/profile in your browser. This should present
5052
you with a response highlighting your logged in role (null) correctly as you are not logged in
51-
4. Navigate to `http://localhost:7987/jersey` this should
52-
1. Redirect you to the OIDCS login console to authenticate yourself, once done
53-
2. Redirects you back to the application and should display your users credentials/IDCS information
54-
5. Executing `http://localhost:7987/jersey` displays the user details from IDCS
53+
4. Open `http://localhost:7987/oidc/logout` in your browser. This will log you out from your IDCS and Helidon sessions
5554

5655
## Calling Sample from Postman
5756

examples/security/idcs-login/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@
9090
<groupId>io.helidon.config</groupId>
9191
<artifactId>helidon-config-yaml</artifactId>
9292
</dependency>
93+
<dependency>
94+
<groupId>io.helidon.config</groupId>
95+
<artifactId>helidon-config-yaml</artifactId>
96+
</dependency>
9397
<dependency>
9498
<groupId>org.jboss</groupId>
9599
<artifactId>jandex</artifactId>

examples/security/idcs-login/src/main/java/io/helidon/security/examples/idcs/IdcsMain.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2021 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.util.Optional;
2020

2121
import io.helidon.common.LogConfig;
22+
import io.helidon.common.context.Contexts;
2223
import io.helidon.common.http.MediaType;
2324
import io.helidon.config.Config;
2425
import io.helidon.security.Security;
@@ -57,6 +58,8 @@ public static void main(String[] args) {
5758
Config config = buildConfig();
5859

5960
Security security = Security.create(config.get("security"));
61+
// this is needed for proper encryption/decryption of cookies
62+
Contexts.globalContext().register(security);
6063

6164
Routing.Builder routing = Routing.builder()
6265
.register(WebSecurity.create(security, config.get("security")))
@@ -70,7 +73,8 @@ public static void main(String[] args) {
7073
.flatMap(SecurityContext::user)
7174
.map(Subject::toString)
7275
.orElse("Security context is null"));
73-
});
76+
})
77+
.get("/loggedout", (req, res) -> res.send("You have been logged out"));
7478

7579
theServer = WebServer.create(routing, config.get("server"));
7680

examples/security/idcs-login/src/main/resources/application.yaml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright (c) 2018, 2020 Oracle and/or its affiliates.
2+
# Copyright (c) 2018, 2021 Oracle and/or its affiliates.
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
55
# you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ security:
3131
- abac:
3232
# Adds ABAC Provider - it does not require any configuration
3333
- oidc:
34+
audience: "https://idcs-f936fc684541496a8b004edb789d59e1.identity.pint.oc9qadev.com:443"
3435
client-id: "${security.properties.idcs-client-id}"
3536
client-secret: "${security.properties.idcs-client-secret}"
3637
identity-uri: "${security.properties.idcs-uri}"
@@ -40,6 +41,8 @@ security:
4041
frontend-uri: "${security.properties.frontend-uri}"
4142
# support for non-public signature JWK (and maybe other IDCS specific handling)
4243
server-type: "idcs"
44+
logout-enabled: true
45+
post-logout-uri: "http://localhost:7987/loggedout"
4346
- idcs-role-mapper:
4447
multitenant: false
4548
oidc-config:
@@ -56,6 +59,6 @@ security:
5659
methods: ["get"]
5760
authenticate: true
5861
roles-allowed: ["my_admins"]
59-
abac:
60-
scopes: ["first_scope", "second_scope"]
62+
# abac:
63+
# scopes: ["first_scope", "second_scope"]
6164

examples/security/idcs-login/src/main/resources/logging.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$
2121

2222
.level=INFO
2323
AUDIT.level=FINEST
24-
io.helidon.security.providers.oidc.level=FINEST
24+
io.helidon.security.level=FINEST
25+

0 commit comments

Comments
 (0)