I’ve been talking about using SQL Developer Web to work with your RESTful Web Services. It’s a major new feature of SQL Developer Web 20.3. You really should glance through these posts before continuing on here:

What I haven’t talked about yet are how this works in the Oracle Cloud (Autonomous Database) and the Security features.

So let’s fix that now.

How it works in Oracle Autonomous Database

Pretty much the same as it works in any ORDS and Oracle Database deployment. If you’re logged in to a REST Enabled Schema – you’ll see the REST card in your home screen.

Note that the Oracle Autonomous Shared services were updated in the last day or 5, and the Autonomous Dedicated environments will be upgraded soon. You’ll want to see version (20.3) listed in the About box.

Everything in this post will also apply to ORDS for your On Premises environments as well.

The ORDS/REST/OAuth2 mechanisms aren’t new for Oracle Autonomous Database in the Oracle Cloud. You can see this awesome post from our developer advocate Todd Sharpe, where he covers how to secure your RESTful Web Services with OAuth2.

Todd is great because he always shares ALL of the code he uses for his examples and fancy hello world applications. That includes al of the PL/SQL ORDS API calls he made to secure his service and to create his ORDS OAuth2 Clients.

You can still do that…and probably should have that scripted/source controlled.

What IS new is the User Interface

The first time you enter the REST development interface, you’ll get a tour.

Here’s what I want to show you:

  • secure a RESTFul Web Service
  • create a Role
  • create an OAuth2 Client
  • access the secured RESTful Web Service

Securing a RESTful Web Services

I have a RESTful Web Service, a simple GET that tallies up sales for a given ZipCode. Under the covers, it takes advantage of a newer database feature, SQL Macros (Tutorial on LiveSQL)

You can use whatever RESTful Web Service/REST Enabled Object you want, but when we go to create the ORDS privilege, substitute your module or resource template patterns for mine.

You can run this code in your REST Enabled schema using your SQL worksheet. The Sales History (SH) data is already available to all of your Oracle Autonomous instances.

 f_results sys_refcursor;
-- n is number of decimal points precision for amount sold and t_observed stats
-- takes advantage of the statisticall function ROLLUP command

 open f_results for
       round(avg(DECODE(CUST_GENDER, 'M', AMOUNT_SOLD,
                  null)), n)                             SOLD_TO_MEN,
       round(avg(DECODE(CUST_GENDER, 'F', AMOUNT_SOLD,
                  null)), n)                            SOLD_TO_WOMEN,
       round(stats_t_test_indep(CUST_GENDER, AMOUNT_SOLD, 'STATISTIC',
                          'F'), n)                           T_OBSERVED,
       stats_t_test_indep(CUST_GENDER, AMOUNT_SOLD) TWO_SIDED_P_VALUE
  from SH.CUSTOMERS    C,
       SH.SALES        S
 where C.CUST_ID = S.CUST_ID
 group by rollup(CUST_INCOME_LEVEL)
 order by INCOME_LEVEL,
 return f_results;
end f_avg_sales_men_women;

  return q'{
   select cust.cust_postal_code as zip_code,
             sum(amount_sold) as revenue
   from sh.customers cust, sh.sales s
   where cust.cust_postal_code = total_sales.zip_code 
   and s.cust_id = cust.cust_id
   group by cust.cust_postal_code
   order by cust.cust_postal_code

      p_module_name    => 'sales_history',
      p_base_path      => '/sales_history/',
      p_items_per_page => 25,
      p_status         => 'PUBLISHED',
      p_comments       => NULL);

      p_module_name    => 'sales_history',
      p_pattern        => 'mf_breakdowns/:n',
      p_priority       => 0,
      p_etag_type      => 'HASH',
      p_etag_query     => NULL,
      p_comments       => NULL);

      p_module_name    => 'sales_history',
      p_pattern        => 'mf_breakdowns/:n',
      p_method         => 'GET',
      p_source_type    => 'json/query',
      p_items_per_page => 25,
      p_mimes_allowed  => '',
      p_comments       => NULL,
      p_source         => 
  from DUAL');

      p_module_name    => 'sales_history',
      p_pattern        => 'total_sales/:zipcode',
      p_priority       => 0,
      p_etag_type      => 'HASH',
      p_etag_query     => NULL,
      p_comments       => NULL);

      p_module_name    => 'sales_history',
      p_pattern        => 'total_sales/:zipcode',
      p_method         => 'GET',
      p_source_type    => 'json/item',
      p_items_per_page => 25,
      p_mimes_allowed  => '',
      p_comments       => NULL,
      p_source         => 
'select * from total_sales(:zipcode)');


As a quick example, let’s just run the SQL to show off the function for sales in a zipcode.

select * from total_sales(60332)

And if you’re curious to the execution plan…

SQL Macros mean I can use this query very easily in many different PL/SQL implementations.

Once you’ve executed the script I shared with you above, go into the REST development, click on the Modules card, and drill down into the ‘total_sales/:zipcode’ template. You can see I have a GET handler defined.

Copying and pasting the URL into a private/incognito browser, we can see the results of the GET request.

It’s not protected, YET.

Speaking of ‘Protected’ – if a RESTful Web Service isn’t protected, it means that anyone that can reach the URL, is going to be able to use it, and get your data.

Be on the lookout for these in your REST console page –

Something, but not everything is protected.

So let’s get that from ‘Yellow’ to ‘Green’ – we want our REST APIs to require authenticated AND authorized requests.

Create an ORDS Role

On the Security menu of the REST screen, click that, and then the Create Role button.

ORDS Roles are EXTREMELY simple, they are 100% defined by their name.

ORDS Roles are NOT Database Roles. Database Roles contain database privileges that can be assigned to database users. ORDS Roles exist so that they can be assigned to HTTP(S) authenticated users and clients. The ORDS Roles will have ORDS Privileges.

Once the Role is created, we can move onto the ORDS Privilege.

Create an ORDS Privilege

Look up where is says ‘Security’ in the REST toolbar. Click on the dropdown, and select ‘Privileges.’

We’re going to create a new Privilege – click the Create button.

The names aren’t important so long as you use a name that you’ll recognize and understand later. Use comments, they’ll make your fellow developers happy.

The Role we previously created, we’re going to tie that Role to our new privilege. And finally, we’re going to associate this Privilege to the entire sales_history module.

Give it a name, assign it to a role, and tie it to one or more modules or template patterns.

Now that the REST API is protected by the Privilege, if we try to access it again, we’ll get

Even if ORDS knows WHO you are, if you do not have the required ROLE, you don’t get access.

What’s happened, so far

We’ve created a working RESTful Web Service. We’ve used it. We created an ORDS Role and Privilege, and said privilege is tied to the RESTful Web Service’s module. When we try to access the API now, we get a 401.

That’s because we’re not authenticated, or even if we WERE authenticated, we’re not AUTHORIZED. The only way ORDS will let us access these protected resources if if our authenticated user has the appropriate role – ‘SALES’.

Creating the OAuth2 Client

Back to the Security menu, click on OAuth Clients.

We don’t have any yet – so click the Create button.

We’ll get the slider mechanism, and we can start to fill out the details. Don’t use my website and don’t use my email, or I will find you, and I will…not be happy.

The * by the Client Def and Roles pages indicate mandatory elements to create the Client.

The critical thing here is making sure the Role is assigned to our Client. If you forget this, we’ll still be denied access when we try to access the RESTful Web Service.

Exploring our new OAuth2 Client

You should now see your client card. You can:

  • get the client id
  • get the secret
  • get a bearer token
  • get the cURL code to get your bearer token
And here’s a GIF demo 🙂

If you’ve ever read blog posts on ORDS & OAuth2, you’ll see the last steps are running queries in the database to get the Client Id and Client Secret. You can still do that, but I think you’ll find this much more convenient!

Accessing the protected resource

I could do this all in cURL, but I hate cURL. My REST Client (Insomnia) provides a nice, integrated Security mechanism, and I’m going to use that.

Instead of using Basic Auth and providing our database credentials and relying on the ‘SQL Developer’ ORDS Role, we can now use our OAuth2 Client that has the SALES ORDS Role. Much better, and we’re not sharing database credentials with anyone that wants to access the APIs we’ve published.

200, Ok!

Going forward my requests only need the Access token. When it expires, simply get a new one.


I'm a Distinguished Product Manager at Oracle. My mission is to help you and your company be more efficient with our database tools.


  1. Hi Sanjay,
    I think that as soon your db user has created something that is REST-enabled, that data is exposed to some url, which can be made unsecure(public) or secured with oath roles and privilages. REST is like a small app on top of database that can have its roles and privilages, independant of database credentials.

  2. Hi Jeff,

    If ORDS maintains its own clients, roles, privileges – how does it “authenticate” to the database to connect and fetch data without database login credentials? Do ORDS clients/roles/privileges override database credentials?


    • ORDS makes normal database connections and does the db work on your behalf…and does so as the rest enabled schema user.

      The api request has to be authenticated and authorized at the web layer, it never directly touches the database.

Write A Comment