Example: OAuth2 Authorization Code Flow using Membrane
Membrane API Gateway can act as OAuth2 authorization server and client.
Figure1:
Running the Example
In this example we will use Membrane as an authorization server that can authenticate users through a login dialog and a client that communicates with this authentication server on behalf of the user to securely access an unprotected resource.
To run the example go to the $MEMBRANE_HOME/examples/oauth2/membrane folder in the Membrane Service Proxy distribution. Complete the following steps:
- Run the service-proxy.bat/.sh in the authorization_server subfolder.
- Navigate to http://localhost:8000/.well-known/openid-configuration in your browser to see the openid-discovery file.
- Run the service-proxy.bat/.sh in the client subfolder.
- Navigate to http://localhost:2000 in your browser to start the login procedure.
- Log in with john as username and password as password.
- Give consent for sharing information by clicking on accept.
- Observe that you get redirected automatically to the resource.
Details
The following part describes the example in detail.
Authorization server
First take a look at the proxies.xml file in the authorization_server subfolder.
<serviceProxy name="Authorization Server" port="8000"> <oauth2authserver issuer="http://localhost:8000" location="logindialog" consentFile="consentFile.json"> <!-- UserDataProvider is exchangeable, e.g. for a database table --> <staticUserDataProvider> <user username="john" password="password" email="john@predic8.de" /> </staticUserDataProvider> <staticClientList> <client clientId="abc" clientSecret="def" callbackUrl="http://localhost:2000/oauth2callback" /> </staticClientList> <!-- Generates tokens in the given format --> <bearerToken/> <claims value="aud email iss sub username"> <!-- Scopes are defined from the claims exposed above --> <scope id="username" claims="username"/> <scope id="profile" claims="username email"/> </claims> </oauth2authserver> </serviceProxy>
You will see a service proxy that listens on port 7000 for incoming calls, in particular it listens for OAuth2 / OpenID Connect calls.
The oauth2authserver element is an implementation of an OAuth2 authorization server.
oauth2authserver
Attribute Name | Description |
---|---|
location | Folder where the login dialog index.html resides |
issuer | The OpenID Connect issuer and is also the base URL for all OAuth2 / OpenID Connect calls. |
consentFile | JSON file where scopes and claims can be described for usage in the index.html |
staticUserDataProvider
The UserDataProvider is used to define information for a specific user. In this example we use a StaticUserDataProvider that enables us to define the information directly in the configuration file.
staticClientList
The ClientList is used to register clients. In this example we use a StaticClientList that enables us to define the information directly in the configuration file.
bearerToken
The TokenGenerator specifies the type of access token that is used in the OAuth2 / OpenID Connect authorization process. Here the BearerTokenGenerator is used.
claims
The last child element specifies the claims and scopes that should be available for the authorization requester. The value attribute specifies the available claims separated by spaces. With those claims one can build scope child elements that define OAuth2 scopes. The scope child elements consist of the id attribute to give the scope a name and the claims attribute to specify the requests claims separated by spaces. Scopes can only contain claims that are available in the value attribute.
Client
Now take a look at the proxies.xml in the client subfolder.
<serviceProxy name="Resource Service" port="2000"> <!-- Protects a resource with OAuth2 - blocks on invalid login --> <oauth2Resource publicURL="http://localhost:2000/"> <membrane src="http://localhost:8000" clientId="abc" clientSecret="def" scope="openid profile" claims="username" claimsIdt="sub" /> </oauth2Resource> <!-- Use the information from the authentication server and pass it to the resource server (optional) --> <groovy> def oauth2 = exc.properties.oauth2 <!-- Put the eMail into the header X-EMAIL and pass it to the protected server. --> exc.request.header.setValue('X-EMAIL',oauth2.userinfo.email) CONTINUE </groovy> <target host="localhost" port="3000"/> </serviceProxy> <serviceProxy port="3000"> <groovy> exc.setResponse(Response.ok("You accessed the protected resource! Hello " + exc.request.header.getFirstValue("X-EMAIL")).build()) RETURN </groovy> </serviceProxy>
You will see a service proxy that listens on port 2000 for incoming calls to redirect those to the authorization server that is specified. If incoming calls are not already authorized then the client makes sure to initiate the OAuth2 authorization code flow.
The oauth2Resource element is an implementation of an OAuth2 client.
The publicURL attribute specifies the location the client/resource can be publicly called.
The service proxy on port 3000 simulates the resource that is to be protected against unauthorized invocation.
Note: Port 3000 should be inaccessible from outside in production use else someone with knowledge of the endpoint can call it directly.
membrane
The first and only child element is of base type AuthorizationService and it has several attributes that specify how the OAuth2 calls are done. In this example the MembraneAuthorizationService is used that gets the most part of its configuration from openid-discovery files.
Attribute Name | Description |
---|---|
subject | Identifier name for a unique, never changing username. Membrane defaults to username but in OpenID Connect subject should be set to sub |
src | Openid-provider ( in this example this is the issuer attribute in the authorization server ) and gets the openid-discovery file from the issuer |
clientId | Is given to the user at registration from the authorization server |
scope | Specifies the requested scopes |
claims | Individual claims for the request and is only usable with the openid scope. These claims are then available at the userinfo endpoint. |
claimsIdt | Individual claims for the request and is only usable with the openid scope. These claims are then available in the idToken. |
When the OAuth2 / OpenID Connect flow is done everything of importance is collected on the outgoing exchange as a property called oauth2. This property can be used to customize the call to the target e.g. to set the authorization header. In this example we use the Groovy interceptor to put the email address of the user ( that was requested through the profile scope ) as the value of the authorization header.
The oauth2 Exchange Property
The oauth2 property on the exchange object has several properties that can be accessed.
Attribute Name | Description |
---|---|
accessToken | Access token that is generated by the token endpoint |
tokenType | Token type of the given access token ( e.g. bearer ) |
expiresIn | Time in seconds the token is valid for |
receivedAt | The time (Java: LocalDateTime) the token was received |
idToken | id token that is generated by the token endpoint in openid scope |
userinfo | Map with the requested claims |
Help needed?
Do you need any help for this scenario? Then contact us using the Membrane Google Group or send an email to membrane@predic8.com.