OAuth
Seedstack OAuth add-on provides the ability to authenticate and authorize subjects using OAuth and OpenID Connect.
Dependencies
<dependency>
<groupId>org.seedstack.addons.oauth</groupId>
<artifactId>oauth</artifactId>
</dependency>
Show version
dependencies {
compile("org.seedstack.addons.oauth:oauth:3.3.0")
}
In a Web context you also need to add the following dependency (otherwise security filters will be ignored):
<dependency>
<groupId>org.seedstack.seed</groupId>
<artifactId>seed-web-security</artifactId>
</dependency>
Show version
dependencies {
compile("org.seedstack.seed:seed-web-security:3.15.1")
}
Overview
The OAuth 2.0 authorization framework is a protocol that allows a user to grant a third-party website or application access to the user’s protected resources, without necessarily revealing their long-term credentials or even their identity.
OpenID Connect (OIDC) is an identity layer built on top of the OAuth 2.0 framework. It allows third-party applications to verify the identity of the end-user and to obtain basic user profile information. OIDC uses JSON web tokens (JWTs), which you can obtain using flows conforming to the OAuth 2.0 specifications. See our OIDC Handbook for more details.
Roles
An OAuth 2.0 flow has the following roles:
- Resource Owner: entity that can grant access to a protected resource. Typically, this is the end-user.
- Resource Server: server hosting the protected resources. This is the API you want to access.
- Client: application requesting access to a protected resource on behalf of the Resource Owner.
- Authorization Server: server that authenticates the Resource Owner and issues access tokens after getting proper authorization.
Use cases
OAuth 2.0 defines four flows to get an access token. Deciding which one is suited for your case depends mostly on your application type:
- Traditional web application: use the authorization code flow. In this case the application server is the OAuth client which request the tokens. It stores them in the web session, making the application stateful.
- Single-page application (SPA): use the authorization code flow with PKCE which replaces the deprecated implicit
grant flow. In this case the SPA is the OAuth client which request the tokens. They are sent to one or more resource
server(s) in an HTTP
Authorization
header as Bearer tokens. - Native mobile application: also use the authorization code flow with PKCE.
- Machine-to-Machine: use the client credentials flow. In this case, one of the machines acts as the OAuth client and request tokens directly using its client credentials. No end-user is involved in this case. This is the functional equivalent of a technical account.
Usage
The add-on comes with a few components that must be used according to the chosen OAuth flow:
- The
oauth
security filter. It’s used in HTTP-based scenarios and protects URL patterns by requiring a valid access token to let the request go trough. It is also responsible to redirect the end-user to the authorization server login page in the authorization code flow scenario (when a callback URL is configured). - The
oauthCallback
security filter. It’s also used in HTTP-based scenarios and should be placed on its own, on a specific callback URL. It is responsible for exchanging the authorization code for tokens in the authorization code flow. - The
OAuthService
. It’s used in programmatic cases like machine-to-machine or custom scenarios to execute OAuth operations (like token resquest, validation, fetching user info, …) on demand. It is used by the security filters, behind the scenes. - The
OAuthRealm
security realm. This realm will validate tokens, extract relevant data (claims) and map them to SeedStack security data structures. It must be configured as soon as a SeedStack security context should be created from tokens. It is used by the security filters, behind the scenes.
Authorization code flow
The following configuration is typical for traditional Web application using the authorization code flow:
rest:
path: /api
security:
realms: OAuthRealm
web:
urls:
- pattern: /callback
filters: oauthCallback
- pattern: /api/**
filters: oauth
oauth:
discoveryDocument: https://my-authorization-server.com/.well-known/openid-configuration
redirect: ${runtime.web.baseUrl}/callback
scopes: [openid, email, profile]
clientId: ...
clientSecret: ...
- The authorization server is configured through the OpenIDConnect discovery document. If this is not supported by your
authorization server, you’ll have to configure the provider manually in the
security.oauth.provider
section (see below). - The redirect (callback) URL is configured, so you’re in an authorization code flow situation.
- The
oauth
filter will intercept any request to/api/**
and check for an existing security context. If absent, it will check for a bearer token and use it to establish the security context. If absent, it will redirect the request to the authorization server login. - The
oauthCallback
filter will be redirected to by the authorization server upon a successful login. It will exchange the authorization code for tokens and establish the security context, then redirect to the original request, which should then be allowed.
Authorization code with PKCE
The following configuration is typical for Single-Page Application (SPA) or mobile application, using the authorization code flow with PKCE:
security:
realms: OAuthRealm
web:
urls:
- pattern: /**
filters: oauth
oauth:
discoveryDocument: https://my-authorization-server.com/.well-known/openid-configuration
scopes: [openid, email, profile]
allowedAudiences: myApiAudience
- The authorization server is configured through the OpenIDConnect discovery document. If this is not supported by your
authorization server, you’ll have to configure the provider manually in the
security.oauth.provider
section (see below). - The
oauth
filter will intercept any request to/api/**
and check for an existing security context. If absent, it will check for a bearer token and use it to establish the security context. If absent, it will deny the request. - The
allowedAudiences
specifies which audience(s) (access tokenaud
claim) will be allowed.
Client-credentials flow
The following configuration is typical in machine-to-machine scenarios, using the client-credentials flow:
security:
realms: OAuthRealm
oauth:
discoveryDocument: https://my-authorization-server.com/.well-known/openid-configuration
scopes: [openid, email, profile]
clientId: sXpRGGxj3N6ETt0m63Wji161PiBsJVuh
clientSecret: Jn2jAXp6mfa8-sKS3C29FwbXSeD5gIKkrlFG_vUq0IXhxwZOAULL2y4ucnbSD3gF
allowedAudiences: myApiAudience
- No security web filter is involved in this scenario.
- The
allowedAudiences
specifies which audience(s) (access tokenaud
claim) will be allowed.
To programmatically request tokens using client credentials, use the following code:
public class SomeClass {
@Inject
private OAuthService oAuthService;
@Inject
private SecuritySupport securitySupport;
public void someMethod() {
OAuthAuthenticationToken tokens = oAuthService.requestTokensWithClientCredentials("email", "profile");
// Option 1: call a remote API
String bearerToken = "Bearer " + tokens.getAccessToken();
// Option 2: login locally
securitySupport.login(tokens);
}
}
From there you have two options:
- Option 1: use the
getAccessToken()
method on the returned object to access a protected resource, like a remote API. - Option 2: use the returned object to login locally using
securitySupport.login(tokens)
method.
All configuration options
All configuration options are described below:
security:
oauth:
# This url defines how clients dynamically discover information about authorization server.
discoveryDocument: (Absolute url as a String)
# This sections allows to manually configure the provider details (and/or override discovered information)
provider:
# Authorization endpoint
authorization: (Absolute url as a String)
# Token endpoint
token: (Absolute url as a String)
# Revocation endpoint
revocation: (Absolute url as a String)
# User info endpoint
userInfo: (Absolute url as a String)
# JWKS url
jwks: (Absolute url as a String)
# Issuer
issuer: (String)
# This sections allows to manually configure accepted cryptographic algorithms
algorithms:
# Access token signing algorithm (RS256 by default)
accessSigningAlgorithm: (String)
# Id token signing algorithm (RS256 by default)
idSigningAlgorithm: (String)
# If true, unsecured (unsigned) tokens are accepted (false by default)
plainTokenAllowed: (boolean)
# Redirection URI when configured for authorization code flow
redirect: (Absolute url in String format)
# Client identifier when the application acts as OAuth/OpenIdConnect client
clientId: (String)
# Client secret when the application acts as OAuth/OpenIdConnect client
clientSecret: (String)
# Requested scopes when the application acts as OAuth/OpenIdConnect client
scopes: (List of comma separated String values)
# Required claims in the access token (["sub"] by default)
requiredClaims: (Set<String>)
# Prohibited claims in the access token (empty set by default)
prohibitedClaims: (Set<String>)
# Allowed audiences for the access token ("aud" claim, empty set by default)
allowedAudiences: (Set<String>)
# Custom parameters to be sent to the authorization server when requesting tokens (as query params)
customParameters: (Map<String, List<String>>)
# Class implementing validation for opaque access tokens (default to userInfo request if endpoint is configured)
accessTokenValidator: (Class<? extends AccessTokenValidator>)
# If true, the user info endpoint will be requested if it is configured to enrich principals with subject personal claims (false by default)
autoFetchUserInfo: (boolean)
# If true, the scopes will be treated as realm roles instead of direct subject permissions roles (false by default)
treatScopesAsRoles: (boolean)
To dump the security.oauth
configuration options:
mvn -q -Dargs="security.oauth" seedstack:config
Examples
Working examples for the three common scenarios described in this page are available at https://github.com/seedstack/samples/tree/master/addons/oauth.
Advanced configuration
Scopes interpretation
By default the add-on will treat OAuth scopes as subject permission (like order:refund
, product:edit
, …). While this is the obvious interpretation of the OAuth protocol, it bypasses the usual SeedStack roles/permissions mapping mechanism. If you want to use this mechanism, set the treatScopesAsRoles
option to true
:
oauth:
treatScopesAsRoles: true
Scopes will then be used as realm roles that can be mapped to applicative roles, which in turn can be resolved to permissions.
Audiences
It is good practice to only allow OAuth tokens that are intended for your application or API. In that spirit, the token validation mechanism has a default audience check: it will only allow tokens that have an aud
containing the application identifier (application.id
config option). You cannot always make the application id match the aud
claim, so you can override the allowed audiences like this:
oauth:
allowedAudiences: [ 'myAudience' ]
If you want to allow tokens without audience, add a null to the set of allowed audiences:
oauth:
allowedAudiences: [ ~ ]