D Creating an Image Gallery
This tutorial explains an extended example that builds an image gallery service for storing and retrieving images. This tutorial uses Oracle Application Express.
Topics:
See Also:
To do this tutorial, you must be familiar with the concepts and techniques covered in Developing Oracle REST Data Services Applications.
D.1 Before You Begin
This section describes some common conventions used in this example as well as best practices regarding API entry points.
Topics:
D.1.1 About URIs
Throughout this example, URIs and URI Templates are referenced using an abbreviated form that omits the host name, context root and workspace path prefix. Consider the following example:
gallery/images/
To access this URI in your Web browser, you would use a URI in the following format:
https://<host>:<port>/ords/<workspace>/gallery/images/
where
-
<host>
is the host on which Oracle REST Data Services is running. -
<port>
is the port on which Oracle REST Data Services is listening. -
/ords
is the context root where Oracle REST Data Services is deployed. -
/<workspace>/
is the workspace path prefix of the Oracle Application Express workspace where the RESTful Service is defined.
D.1.2 About Browser Support
This example uses many modern features defined in HTML5 and related specifications. It has only been tested in Mozilla Firefox and Google Chrome. It has not been tested in Microsoft Internet Explorer or on smart-phone/tablet web browsers. Please use recent versions of either Mozilla Firefox or Google Chrome for this example.
D.1.3 Creating an Application Express Workspace
To follow the instructions for creation the Gallery example application and related objects, first, create a new Oracle Application Express Workspace (in Full Development mode). See the Oracle Application Express Documentation for details on how to do this.
Call the workspace resteasy
and call the administrator user of the workspace resteasy_admin
. Ensure the resteasy_admin
user is a a member of the RESTful Services
user group.
D.3 Creating the Gallery RESTful Service Module
To create the Gallery RESTful services module, follow these steps:
-
Navigate to SQL Workshop and then RESTful Services.
-
Click Create on the right side, and enter the following information:
-
Name:
gallery.example
-
URI Prefix:
gallery/
-
URI Template:
images/
-
Method:
POST
-
Source: Enter or copy and paste in the following:
declare image_id integer; begin insert into gallery (title,content_type,image) values (:title,:content_type,:body) returning id into image_id; :status := 201; :location := image_id; end;
-
-
Click Create Module.
-
Click the
POST
handler underimages/
-
For Requires Secure Access, select
No
. -
Click Create Parameter, and enter the following:
-
Name:
Slug
-
Bind Variable Name:
title
-
-
Click Create.
-
Click Create Parameter on the bottom right, and enter the following information:
-
Name:
X-APEX-FORWARD
-
Bind Variable Name:
location
-
Access Method:
OUT
-
-
Click Create.
-
Click Create Parameter on the bottom right, and enter the following information:
-
Name:
X-APEX-STATUS-CODE
-
Bind Variable Name:
status
-
Access Method:
OUT
-
Parameter Type:
Integer
-
-
Click Create.
At this point you have created the module with a single service that can store new images. Next, add a service to display the list of stored images:
-
Navigate to SQL Workshop and then RESTful Services.
-
Click the module named
gallery.example
. -
Click Create Handler under
images/
, and enter the following information:-
Method:
GET
-
Source Type:
Feed
-
Requires Secure Access:
No
-
Source: Enter or copy and paste in the following:
select id,title,content_type from gallery order by id desc
-
-
Click Create.
At this point you have created the service to store and list images. Next, add a service to display individual images:
-
Navigate to SQL Workshop and then RESTful Services.
-
Click the module named
gallery.example
. -
Click Create Template under gallery.example, and enter the following information:
-
URI Template:
images/{id}
-
-
Click Create.
-
Click Create Handler under images/{id}, and enter the following information:
-
Method:
GET
-
Source Type:
Media Resource
-
Requires Secure Access:
No
-
Source: Enter or copy and paste in the following:
select content_type, image from gallery where id = :id
-
-
Click Create.
D.4 Trying Out the Gallery RESTful Service
To try out the Gallery RESTful Service, follow these steps:
To create an Oracle Application Express application to enable users to add and view images in the gallery, see Creating the Gallery Application.
D.5 Creating the Gallery Application
To create an Oracle Application Express application that uses the gallery RESTful Services, follow these steps:
D.7 Securing the Gallery RESTful Services
It is not wise to allow public access to the image uploading service, and it is probably not ideal to allow public access to the images in the gallery either. Therefore, you should protect access to the RESTful services.
RESTful Services support two kinds of authentication:
-
First Party Authentication. This is authentication intended to be used by the party who created the RESTful service, enabling an Application Express application to easily consume a protected RESTful service. The application must be located with the RESTful service, that is, it must be located in the same Oracle Application Express workspace. The application must use the standard Oracle Application Express authentication.
-
Third Party Authentication. This is authentication intended to be used by third party applications not related to the party who created the RESTful service. Third party authentication relies on the OAuth 2.0 protocol.
Topics:
D.7.1 Protecting the RESTful Services
To protect the RESTful services, follow these steps:
-
Navigate to SQL Workshop and then RESTful Services.
-
Click RESTful Service Privileges in the section labeled Tasks.
-
Click Create, and enter the following:
-
Name:
example.gallery
-
Label:
Gallery Access
-
Assigned Groups:
RESTful Services
-
Description:
View and Post images in the Gallery
-
Protected Modules:
gallery.example
-
-
Click Create.
To check that access to the RESTful Service is now restricted, follow these steps:
-
Navigate to SQL Workshop and then RESTful Services.
-
Click the module named
gallery.example
. -
Click the
GET
handler located underimages/
. -
Click Test.
The URI in the following format should be displayed in the browser:
https://<host>:<port>/ords/resteasy/gallery/images
An error page should be displayed with the error message:
401 Unauthorized.
See Also:
This is the expected result, because a protected RESTful Service cannot be accessed unless proper credentials are provided. To add the required credentials to the request, see Modifying the Application to Use First Party Authentication
D.7.2 Modifying the Application to Use First Party Authentication
First Party Authentication relies on the cookie and user session established by the Application Express application, but Oracle REST Data Services needs additional information to enable it to verify the cookie. It needs to know the application ID and the current session ID. This information is always known to the Application Express application, and must be included with the request made to the RESTful service by adding the custom Apex-Session
HTTP header to each request sent to a RESTful Service. The application ID and session ID are sent as the value of the header, separated from each other by a comma delimiter. For example:
GET /ords/resteasy/gallery/images/ Host: server.example.com Apex-Session: 102,6028968452563
Sometimes it is not possible to include a custom header in the HTTP request. For example, when displaying an image in an HTML page using the <img>
tag, an alternative mechanism is used for these scenarios. The application ID and session ID are included in a query parameter named _apex_session
, which is added to the Request URI, which contains the application ID and session ID separated by a comma. For example:
<img src="graphics/101?_apex_session=102,6028968452563">
Note that this approach must only be used when it is not possible to use a custom header. Otherwise, this approach is discouraged because of the increased risk of the session ID being inadvertently stored or disclosed due to its inclusion in the URI.
To modify the application to add the first party authentication information to each request, follow these steps:
-
Navigate to Application Builder.
-
Click the Edit button beside the Image Gallery application.
-
Click the first page, named
Home
. -
Under Page click the Edit icon, and click the JavaScript tab.
-
Add the following at the start of the Function and Global Variable Declaration field:
function setApexSession(pathOrXhr) { var appId = $v('pFlowId'); var sessionId = $v('pInstance'); var apexSession = appId + ',' + sessionId; if ( typeof pathOrXhr === 'string' ) { var path = pathOrXhr; if ( path.indexOf('?') == -1 ) { path = path + '?_apex_session=' + apexSession; } else { path = path + '&_apex_session=' + apexSession; } return path; } else { var xhr = pathOrXhr; xhr.setRequestHeader('Apex-Session',apexSession); return xhr; } }
-
This defines a JavaScript function named
setApexSession()
which will add the first party authentication information to anXMLHttpRequest
object or a string containing a path.Now you must modify the existing JavaScript code to add call this function when appropriate.
-
After the line reading
xhr.open('POST',url,true);
, add the following line:setApexSession(xhr);
-
After the line reading
xhr.open('GET', gallery_url);
, add the following line:setApexSession(xhr);
-
Change the line reading
var uri = item.uri['$ref'];
to:var uri = setApexSession(item.uri['$ref']);
-
Click Apply Changes.
-
Try running the application as before. It should work, because it is now providing the RESTful Services with the required authentication information.
D.8 Accessing the RESTful Services from a Third Party Application
If third parties want to consume and use the Gallery RESTful services, they must register the third party application in order to gain OAuth 2.0 credentials, which can then be used to initiate an interactive process by which users can authorize the third party application to access the RESTful Services on their behalf.
Once an application is registered, it can then acquire an access token. The access token must be provided with each request to a protected RESTful Service. Oracle REST Data Services verifies the access token before allowing access to the RESTful service.
OAuth 2.0 defines a number of different protocol flows that can be used by applications to acquire an access token. Oracle REST Data Services supports two of these protocol flows:
-
Authorization Code. This flow is used when the third party application is able to keep its client credentials secure, for example, a third party website that is properly secured.
-
Implicit Grant. This flow is used when the third party application cannot assure that its credentials would remain secret, for example, a JavaScript-based browser application or a native smartphone application.
The first step is to register the third party application. To demonstrate this, you will create a user representing the third party developer, and then use that user to register an application.
The steps in the related topics create a user in the RESTEASY
workspace user repository and perform related actions.
Note:
In addition to authenticating users defined in workspace user repositories, Oracle REST Data Services can also authenticate against any user repository accessible from WebLogic Server. For information, see Authenticating Against WebLogic Server.
Topics:
D.8.1 Creating the Third Party Developer User
To create the third party developer user (the user account for the third party developer who wants to register an application to access the RESTful services), follow these steps:
D.8.2 Registering the Third Party Application
To register the third party application to use the Implicit Grant
OAuth 2.0 protocol flow, follow these steps:
D.8.3 Acquiring an Access Token
To acquire an access token, a user must be prompted to approve access. To initiate the approval process, direct the user to the approval page using the following URI:
https://server:port/ords/resteasy/oauth2/auth?response_type=token&\ client_id=CLIENT_IDENTIFIER&\ state=STATE
where:
-
CLIENT_IDENTIFIER
is the Client Identifier assigned to the application when it was registered. -
STATE
is a unique value generated by the application used to prevent Cross Site Request Forgery (CSRF) attacks.
Note the following about the Oracle REST Data Services OAuth 2.0 implementation:
-
The OAuth 2.0 specification allows two optional parameters to be supplied in the above request:
-
redirect_uri
: Identifies the location where the authorization server will redirect back to after the user has approved/denied access. -
scope
: Identifies the RESTful Service Privileges that the client wishes to access.
Oracle REST Data Services does not support either of these parameters: both of these values are specified when the client is registered, so it would be redundant to repeat them here. Any values supplied for these parameters will be ignored.
-
-
The OAuth 2.0 specification recommends the use of the
state
parameter, but Oracle REST Data Services requires the use of the parameter because of its importance in helping to prevent CSRF attacks. -
The response type is also specified when the application is registered, and thus the
response_type
parameter is also redundant; however, the OAuth 2.0 specification states the parameter is always required, so it must be included. It is an error if theresponse_type
value differs from the registered response type.
When the preceding URI is accessed in a browser, the user is prompted to sign on, and then prompted to review the application's request for access and choose whether to approve or deny access.
If the user approves the request, then the browser will be redirected back to the registered redirect URI, and the access token will be encoded in the fragment portion of the URI:
https://example.org/#token_type=bearer&\ access_token=ACCESS_TOKEN&\ expires_in=TOKEN_LIFETIME&\ state=STATE
where:
-
example.org
is used for illustrative purposes only. In a real applicationexample.org
will be replaced with the URL of the third party application that is requesting access. -
ACCESS_TOKEN
is the unique, unguessable access token assigned to the current user session, and which must be provided with subsequent requests to the RESTful service. -
TOKEN_LIFETIME
is the number of seconds for which the access token is valid. -
STATE
is the unique value supplied by the application at the start of the authorization flow. If the returnedstate
value does not match the initialstate
value, then there is an error condition, and the access token must not be used, because it is possible an attacker is attempting to subvert the authorization process through a CSRF attack.
Note:
You can modify the default OAuth access token duration (or lifetime) for all the generated access tokens. To achieve this, add thesecurity.oauth.tokenLifetime
entry to the defaults.xml
configuration file in the following way, with the OAuth access token duration specified in seconds:
<entry key="security.oauth.tokenLifetimeā>600</entry>
If the user denies the request, or the user is not authorized to access the RESTful Service, the browser will be redirected back to the registered redirect URI, and an error message will be encoded in the fragment portion of the URI:
https://example.org/#error=access_denied&state=STATE
where:
-
error=access_denied
informs the client that the user is not authorized to access the RESTful Service, or chose not to approve access to the application. -
STATE
is the unique value supplied by the application at the start of the authorization flow. If the returnedstate
value does not match the initialstate
value, then there is an error condition, the client should ignore this response. It is possible an attacker is attempting to subvert the authorization process via a CSRF attack.
D.8.4 Using an Access Token
After the application has acquired an access token, the access token must be included with each request made to the protected RESTful service. To do this, an Authorization
header is added to the HTTP request, with the following syntax:
Authorization: Bearer ACCESS_TOKEN
where:
-
ACCESS_TOKEN
is the access token value.
For example, a JavaScript-based browser application might invoke the Gallery service as follows:
var accessToken = ... /* initialize with the value of the access token */ var xhr = new XMLHttpRequest(); xhr.open('GET', 'https://server:port/ords/resteasy/gallery/images/',true); /* Add the Access Token to the request */ xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken); xhr.onload = function(e) { /* logic to process the returned JSON document */ ... }; xhr.send();
The preceding example uses the XMLHttpRequest.setRequestHeader(name,value)
function to add the Authorization
header to the HTTP request. If the access token is valid, then the server will respond with a JSON document listing the images in the gallery.
D.8.5 About Browser Origins
One of the key security concepts of web browsers is the Same Origin Policy, which permits scripts running on pages originating from the same web site (an Origin) to access each other's data with no restrictions, but prevents access to data originating from other web sites.
An origin is defined by the protocol, host name and port of a web-site. For example https://example.com
is one origin and https://another.example.com
is a different origin, because the host name differs. Similarly, http://example.com
is a different origin than https://example.com
because the protocol differs. Finally, http://example.com
is a different origin from http://example.com:8080
because the port differs.
For example, if a third party client of the Gallery RESTful service is located at:
https://thirdparty.com/gallery.html
and the Gallery RESTful service is located at:
https://example.com/ords/resteasy/gallery/images/
then the Same Origin Policy will prevent gallery.html
making an XMLHttpRequest
to https://example.com/ords/resteasy/gallery/images/
, because scripts in the https://thirdparty.com
origin can only access data from that same origin, and https://example.com
is clearly a different origin.
This is proper if the authors of https://example.com
do not trust the authors of https://thirdparty.com
. However, if the authors do have reason to trust each other, then the Same Origin Policy is too restrictive. Fortunately, a protocol called Cross Origin Resource Sharing (CORS), provides a means for https://example.com
to inform the web browser that it trusts https://thirdparty.com
and thus to instruct the browser to permit gallery.html
to make an XMLHttpRequest
to https://example.com/ords/resteasy/gallery/images/
.
D.8.6 Configuring a RESTful Service for Cross Origin Resource Sharing
To configure a RESTful service for Cross Origin Resource Sharing, follow these steps:
- Navigate to SQL Workshop and then RESTful Services.
- Click the module named
gallery.example
. - For Origins Allowed, enter the origins that are permitted to access the RESTful service (origins are separated by a comma).
- Press Apply Changes
D.8.7 Acquiring a Token Using the Authorization Code Protocol Flow
Other sections have explained acquiring an access token using the OAuth 2.0 Implicit protocol flow. This section explains how to do the same using the Authorization Code protocol flow. The process is slightly more involved than for the Implicit protocol flow, because it requires exchanging an authorization code for an access token.
This section will mimic this exchange process using cURL.
Topics:
D.8.7.2 Acquiring an Authorization Code
The first step in the Authorization Code protocol flow is to acquire an authorization code. An authorization code is a short lived token that when presented along with the application's client identifier and secret can be exchanged for an access token.
To acquire an access token, the user must be prompted to approve access. To initiate the approval process, direct the user to the approval page using a URI in the following format:
https://server:port/ords/resteasy/oauth2/auth?response_type=code&\ client_id=CLIENT_IDENTIFIER&\ state=STATE
where:
-
CLIENT_IDENTIFIER
is the Client Identifier assigned to the application when it was registered. -
STATE
is a unique value generated by the application used to prevent Cross Site Request Forgery (CSRF) attacks.
If the user approves the request, then the browser will be redirected back to the registered redirect URI, and the access token will be encoded in the query string portion of the URI:
https://gallery.example.demo?code=AUTHORIZATION_CODE&state=STATE
where:
-
AUTHORIZATION_CODE
is the authorization code value. -
STATE
is the unique value supplied by the application at the start of the authorization flow. If the returnedstate
value does not match the initialstate
value, then there is an error condition, the authorization code must not be used. It is possible an attacker is attempting to subvert the authorization process via a CSRF attack.Because the registered
https://gallery.example.demo
redirect URI does not exist, the browser will report a server not found error, but for the purposes of this example, this does not matter, because you can still see the authorization code value encoded in the URI. Note the value of thecode
parameter, because it will be used while Exchanging an Authorization Code for an Access Token.
D.8.7.3 Exchanging an Authorization Code for an Access Token
In this section you will use cURL to exchange the authorization code for an access token. To exchange an authorization code the application must make an HTTP request to the Oracle REST Data Services OAuth 2.0 token endpoint, providing the authorization code and its client identifier and secret. If the credentials are correct, Oracle REST Data Services responds with a JSON document containing the access token. Note that the application makes the HTTP request from its server side (where the client identifier and secret are securely stored) directly to Oracle REST Data Services; the web-browser is not involved at all in this step of the protocol flow.
Use a cURL command in the following format to exchange the authorization code for an access token:
curl -i -d "grant_type=authorization_code&code=AUTHORIZATION_CODE" \ --user CLIENT_IDENTIFER:CLIENT_SECRET \ https://server:port/ords/resteasy/oauth2/token
where:
-
AUTHORIZATION_CODE
is the authorization code value (which was encoded in thecode
parameter of the query string in the redirect URI in the previous section). -
CLIENT_IDENTIFER
is the client identifier value. -
CLIENT_SECRET
is the client secret value.
cURL translates the above commands into an HTTP request like the following:
POST /ords/resteasy/oauth2/token HTTP/1.1 Authorization: Basic Q0xJRU5UX0lERU5USUZJRVI6Q0xJRU5UX1NFQ1JFVA== Host: server:port Accept: */* Content-Length: 59 Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=AUTHORIZATION_CODE
where:
-
The request is an HTTP
POST
to theoauth2/token
OAuth 2.0 token endpoint. -
The
Authorization
header uses the HTTPBASIC
authentication protocol to encode the client identifier and secret to assert the application's identity. -
The
Content-Type
of the request is form data (application/x-www-form-urlencoded
) and the content of the request is the form data asserting the OAuth 2.0 token grant type and the OAuth 2.0 authorization code value.
The preceding HTTP request will produce a response like the following:
HTTP/1.1 200 OK ETag: "..." Content-Type: application/json { "access_token":"04tss-gM35uOeQzR_2ve4Q..", "token_type":"bearer", "expires_in":3600, "refresh_token":"UX4FVHhPFJl6GokvTXYw0A.." }
The response is a JSON document containing the access token along with a refresh token. After the application has acquired an access token, the access token must be included with each request made to the protected RESTful Service. To do this an Authorization
header is added to the HTTP request, with the following syntax:
Authorization: Bearer ACCESS_TOKEN
Related Topics
D.8.7.4 Extending OAuth 2.0 Session Duration
To extend the lifetime of an OAuth 2.0 session, a refresh token can be exchanged for a new access token with a new expiration time. Note that refresh tokens are only issued for the Authorization Code protocol flow.
The application makes a similar request to that used to exchange an authorization code for an access token. Use a cURL command in the following format to exchange the refresh token for an access token:
curl -i -d "grant_type=refresh_token&refresh_token=REFRESH_TOKEN" \ --user CLIENT_IDENTIFER:CLIENT_SECRET \ https://server:port/ords/resteasy/oauth2/token
where:
-
REFRESH_TOKEN
is the refresh token value returned when the access token was initially issued. -
CLIENT_IDENTIFER
is the client identifier value. -
CLIENT_SECRET
is the client secret value.
cURL translates the above commands into an HTTP request like the following:
POST /ords/resteasy/oauth2/token HTTP/1.1 Authorization: Basic Q0xJRU5UX0lERU5USUZJRVI6Q0xJRU5UX1NFQ1JFVA== Host: server:port Accept: */* Content-Length: 53 Content-Type: application/x-www-form-urlencoded grant_type=refresh_token&refresh_token=REFRESH_TOKEN
where:
-
The request is an HTTP
POST
to theoauth2/token
OAuth 2.0 token endpoint. -
The
Authorization
header uses the HTTPBASIC
authentication protocol to encode the client identifier and secret to assert the application's identity. -
The
Content-Type
of the request is form data (application/x-www-form-urlencoded
) and the content of the request is the form data asserting the OAuth 2.0 token grant type and the refresh token value.
The preceding HTTP request will produce a response like the following:
HTTP/1.1 200 OK ETag: "..." Content-Type: application/json { "access_token":"hECH_Fc7os2KtXT4pDfkzw..", "token_type":"bearer", "expires_in":3600, "refresh_token":"-7OBQKc_gUQG93ZHCi08Hg.." }
The response is a JSON document containing the new access token along with a new refresh token. The existing access token and refresh token are invalidated, and any attempt to access a service using the old access token will fail.
D.8.8 About Securing the Access Token
In OAuth 2.0 the access token is the sole credential required to provide access to a protected service. It is, therefore, essential to keep the access token secure. Follow these guidelines to help keep the token secure:
-
It is strongly recommended to use HTTPS for all protected RESTful Services. This prevents snooping attacks where an attacker may be able to steal access tokens by eavesdropping on insecure channels. It also prevents attackers from viewing the sensitive data that may be present in the payload of the requests.
-
Ensure that the client application is not located in a browser origin with other applications or scripts that cannot be trusted. For example assume that user Alice has a client application hosted at the following location:
https://sharedhosting.com/alice/application
If another user (such as Fred) is also able to host his application in the same origin, for example, at:
https://sharedhosting.com/fred/trouble
then it will be easy for
/fred/trouble
to steal any access token acquired by/alice/application
, because they share the same originhttps://sharedhost.com
, and thus the browser will not prevent either application from accessing the other's data.To protect against this scenario, Alice's application must be deployed in its own origin, for example:
https://alice.sharedhosting.com/application
or:
https://application.alice.sharedhosting.com
or:
https://aliceapp.com