Skip to content

Commit

Permalink
Merge pull request #37166 from sberyozkin/custom_bearer_authorization…
Browse files Browse the repository at this point in the history
…_scheme

Support custom Authorization schemes for OIDC bearer tokens
  • Loading branch information
sberyozkin authored Nov 20, 2023
2 parents 6c11d36 + 7a8d035 commit d29c7ef
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1477,6 +1477,12 @@ public static Token fromAudience(String... audience) {
@ConfigItem
public Optional<String> header = Optional.empty();

/**
* HTTP Authorization header scheme.
*/
@ConfigItem(defaultValue = OidcConstants.BEARER_SCHEME)
public String authorizationScheme = OidcConstants.BEARER_SCHEME;

/**
* Required signature algorithm.
* OIDC providers support many signature algorithms but if necessary you can restrict
Expand Down Expand Up @@ -1697,6 +1703,14 @@ public boolean isSubjectRequired() {
public void setSubjectRequired(boolean subjectRequired) {
this.subjectRequired = subjectRequired;
}

public String getAuthorizationScheme() {
return authorizationScheme;
}

public void setAuthorizationScheme(String authorizationScheme) {
this.authorizationScheme = authorizationScheme;
}
}

public static enum ApplicationType {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package io.quarkus.oidc.runtime;

import java.util.function.Function;

import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.common.runtime.OidcConstants;
import io.quarkus.security.identity.IdentityProviderManager;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.vertx.http.runtime.security.ChallengeData;
Expand All @@ -15,9 +16,6 @@

public class BearerAuthenticationMechanism extends AbstractOidcAuthenticationMechanism {

protected static final ChallengeData UNAUTHORIZED_CHALLENGE = new ChallengeData(HttpResponseStatus.UNAUTHORIZED.code(),
HttpHeaderNames.WWW_AUTHENTICATE, OidcConstants.BEARER_SCHEME);

public Uni<SecurityIdentity> authenticate(RoutingContext context,
IdentityProviderManager identityProviderManager, OidcTenantConfig oidcTenantConfig) {
String token = extractBearerToken(context, oidcTenantConfig);
Expand All @@ -29,7 +27,14 @@ public Uni<SecurityIdentity> authenticate(RoutingContext context,
}

public Uni<ChallengeData> getChallenge(RoutingContext context) {
return Uni.createFrom().item(UNAUTHORIZED_CHALLENGE);
Uni<TenantConfigContext> tenantContext = resolver.resolveContext(context);
return tenantContext.onItem().transformToUni(new Function<TenantConfigContext, Uni<? extends ChallengeData>>() {
@Override
public Uni<ChallengeData> apply(TenantConfigContext tenantContext) {
return Uni.createFrom().item(new ChallengeData(HttpResponseStatus.UNAUTHORIZED.code(),
HttpHeaderNames.WWW_AUTHENTICATE, tenantContext.oidcConfig.token.authorizationScheme));
}
});
}

private String extractBearerToken(RoutingContext context, OidcTenantConfig oidcConfig) {
Expand All @@ -49,7 +54,7 @@ private String extractBearerToken(RoutingContext context, OidcTenantConfig oidcC
return headerValue;
}

if (!OidcConstants.BEARER_SCHEME.equalsIgnoreCase(scheme)) {
if (!oidcConfig.token.authorizationScheme.equalsIgnoreCase(scheme)) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@

@ApplicationScoped
public class OidcAuthenticationMechanism implements HttpAuthenticationMechanism {
private static HttpCredentialTransport OIDC_SERVICE_TRANSPORT = new HttpCredentialTransport(
HttpCredentialTransport.Type.AUTHORIZATION, OidcConstants.BEARER_SCHEME);
private static HttpCredentialTransport OIDC_WEB_APP_TRANSPORT = new HttpCredentialTransport(
HttpCredentialTransport.Type.AUTHORIZATION_CODE, OidcConstants.CODE_FLOW_CODE);

Expand Down Expand Up @@ -105,7 +103,8 @@ public HttpCredentialTransport apply(OidcTenantConfig oidcTenantConfig) {
return null;
}
return isWebApp(context, oidcTenantConfig) ? OIDC_WEB_APP_TRANSPORT
: OIDC_SERVICE_TRANSPORT;
: new HttpCredentialTransport(
HttpCredentialTransport.Type.AUTHORIZATION, oidcTenantConfig.token.authorizationScheme);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public String resolve(RoutingContext context) {
if (path.endsWith("bearer")) {
return "bearer";
}
if (path.endsWith("bearer-id")) {
return "bearer-id";
}
if (path.endsWith("bearer-required-algorithm")) {
return "bearer-required-algorithm";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ public User principalName() {
return new User(identity.getPrincipal().getName());
}

@GET
@Path("/me/bearer-id")
@RolesAllowed("user")
@Produces(MediaType.APPLICATION_JSON)
public User principalNameId() {
return new User(identity.getPrincipal().getName());
}

@GET
@Path("/preferredUserName/bearer")
@RolesAllowed("user")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ quarkus.oidc.bearer.credentials.secret=secret
quarkus.oidc.bearer.token.audience=https://service.example.com
quarkus.oidc.bearer.allow-token-introspection-cache=false

quarkus.oidc.bearer-id.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.bearer-id.client-id=quarkus-app
quarkus.oidc.bearer-id.credentials.secret=secret
quarkus.oidc.bearer-id.allow-token-introspection-cache=false
quarkus.oidc.bearer-id.token.authorization-scheme=ID

quarkus.oidc.bearer-required-algorithm.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.bearer-required-algorithm.client-id=quarkus-app
quarkus.oidc.bearer-required-algorithm.credentials.secret=secret
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,17 @@ public void testExpiredBearerToken() {
.header("WWW-Authenticate", equalTo("Bearer"));
}

@Test
public void testBearerToken() {
String token = getAccessToken("alice", Set.of("user"));

RestAssured.given().auth().oauth2(token).when()
.get("/api/users/me/bearer")
.then()
.statusCode(200)
.body(Matchers.containsString("alice"));
}

@Test
public void testBearerTokenWrongIssuer() {
String token = getAccessTokenWrongIssuer("alice", Set.of("user"));
Expand All @@ -284,6 +295,38 @@ public void testBearerTokenWrongAudience() {
.header("WWW-Authenticate", equalTo("Bearer"));
}

@Test
public void testBearerTokenIdScheme() {
String token = getAccessToken("alice", Set.of("user"));

RestAssured.given().header("Authorization", "ID " + token).when()
.get("/api/users/me/bearer-id")
.then()
.statusCode(200)
.body(Matchers.containsString("alice"));
}

@Test
public void testBearerTokenIdSchemeButBearerSchemeIsUsed() {
String token = getAccessToken("alice", Set.of("user"));

RestAssured.given().auth().oauth2(token).when()
.get("/api/users/me/bearer-id")
.then()
.statusCode(401);
}

@Test
public void testBearerTokenIdSchemeWrongIssuer() {
String token = getAccessTokenWrongIssuer("alice", Set.of("user"));

RestAssured.given().auth().oauth2(token).when()
.get("/api/users/me/bearer-id")
.then()
.statusCode(401)
.header("WWW-Authenticate", equalTo("ID"));
}

@Test
public void testAcquiringIdentityOutsideOfHttpRequest() {
String tenant = "bearer";
Expand Down

0 comments on commit d29c7ef

Please sign in to comment.