Tag: OCI Domain JWTs

  • 401 Unauthorized invalid_token – troubleshooting Oracle Cloud IAM JWTs with ORDS

    401 Unauthorized invalid_token – troubleshooting Oracle Cloud IAM JWTs with ORDS

    Symptoms

    You are able to request an access token from OCI IAM. Yet, when you issue the subsequent request to your target resource (an ORDS endpoint), you receive the following message (error="invalid_token"): 

    WWW-Authenticate: Bearer realm="Oracle REST Data Services", error="invalid_token"

    Actions you’ve taken

    1. You’ve done the following in OCI:
      • Registered an Integrated Application with Oracle Identity and Access Management (IAM)
      • Created a Primary Audience & Scope
      • Obtained your Client ID and Client Secret
      • Configured your networking correctly (or at least have high confidence it’s configured correctly)
      • Acquired all of the correct, relevant URIs:
        • Authorization
        • Access Token
        • JWK
    2. You’ve configured the following in ORDS:
      • Created your JWT Profile according to the docs (or this tutorial)
      • Created the JWT Role and Privilege (which should be the same as the OCI Scope name)
      • And protected your target resource (aka ORDS API)
    3. You’ve placed everything where it should be in your choice of API testing tool (cURL, Postman, Insomnia, etc.).

    YET…you still receive this error="invalid_token" message, it is quite possible that you have not made the JWK URL publically accessible in OCI IAM. 

    Solution

    Here is how you can verify and resolve this issue. First, navigate to your domain, then select Settings.

    If this Configure client access box is unchecked, it is likely the culprit. Check it, then select Save Changes (the button at the bottom of the screen).

    Then, once you re-attempt your HTTP request, ORDS will be able to:

    1. Access the JWK URL (which you’ve included when you created your JWT Profile)
    2. Verify the authenticity of your JWT, and 
    3. Respond with the results from your resource (ORDS endpoint)

    Et voilà! And that’s it, you’re back in business! 

    To-do list

    I think we have some action items, too: 

    • Investigate this error message and see if we can improve the message to the user (we’ve already filed an enhancement request on this)
    • Update the docs to be more specific on this setting and where to find it (a documentation bug has already been filed for this)
    • Determine if this is a good candidate for adding to the troubleshooting section of our guide
    • Update my JWT tutorial

    And that’s all for now. ORDS version 25.1 is right around the corner, so look out for the official release notes and my accompanying blog too!

    Follow

    And don’t forget to follow, like, subscribe, share, taunt, troll, or stalk me!

  • Example: an ORDS API that returns a user-configured JSON object

    Example: an ORDS API that returns a user-configured JSON object

    This actually came about from a Support Request. We were troubleshooting a JWT issue, that had ORDS in the mix. Ultimately, this is looking more and more like a networking issue (maybe something to do with routing rules between the load balancer and the backend server).

    But in the course of our troubleshooting, we wanted to rule out some variables. One of those variables was the JWK URL we were using for the ORDS_SECURITY.CREATE_JWT_PROFILE procedure. So we had this idea to kind of fool ORDS into thinking it was visiting the IDCS (the predecessor to Oracle IAM) JWK URL when, in fact, it was just visiting another ORDS endpoint.

    If you’ve seen my JWT + ORDS tutorials (one for OCI IAM, another for Microsoft Azure), then you know when you create an ORDS JWT Profile, it will look something like this:

    BEGIN
      ORDS_SECURITY.CREATE_JWT_PROFILE(
          p_issuer => 'https://identity.oraclecloud.com/',
          p_audience => 'my_primary_audience' ,
          p_jwk_url =>'https://idcs-10a10a10a10a10a10a10a10a.identity.oraclecloud.com/admin/v1/SigningCert/jwk'
      );
      COMMIT;
    END;
    /

    In the above code block, just know that p_issuer is your Identity Provider (e.g., IAM, IDCS, Microsoft Entra, etc.), p_audience is the Primary Audience (which you created when you registered your client application with your Identity Provider), and the p_jwk_url is where ORDS goes to look up the keys for decoding a JWT.

    The eventual JWT that ORDS receives would include the Issuer and Audience, too. But it would also include other values such as the Scope. That Scope name would be associated with the ORDS Privilege–the privilege you created specifically to use for protecting/associating with your ORDS Resource Module. And ORDS would use all these values (and the JWK “keys” found at that JWK URL to decode the JWT) together to:

    1. Verify you are who you say you are, and
    2. You have the proper Scope (or as ORDS interprets it…the proper privilege) to access this protected resource

    Back to the use case. So, for troubleshooting reasons, we took that JSON object located at that JWK URL and recreated it as an ORDS endpoint. Let me point out that this is cool, but what you should take away from this is that there are tons of things you can do with ORDS, and you have a lot of control over how ORDS will send back information to you, your application, or your web client.

    Here is how we structured the Handler code:

    BEGIN
        HTP.P('{"
        keys": [
            {
                "kty": "RSA",
                "x5t#S256": "Lorem ipsum dolor sit amet__pellentesque elementum",
                "e": "AQAB",
                "x5t": "xwSmccaQZDvAZPOpZPHOiQDlLgo",
                "kid": "SIGNING_KEY",
                "x5c": [
                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce lorem dui, elementum et odio ut, sollicitudin gravida dui. Ut nec dapibus elit. Morbi sit amet blandit justo. Quisque accumsan turpis metus, vel sodales nisl porta vel. Nunc placerat, mi id eleifend pharetra, massa urna imperdiet arcu, vitae dignissim nulla ex sed felis. Maecenas auctor risus ac nisl efficitur, sit amet euismod odio finibus. Praesent lacinia nunc id ex tempor, sed tempor nisl porta. Integer semper efficitur arcu, a dictum quam vulputate tempus. Nulla congue dapibus tortor vel volutpat. Curabitur et sollicitudin purus. Mauris quis aliquam augue. Nulla ac leo tristique, ultricies mauris sit amet, ultrices quam. Praesent placerat a lectus sit amet dapibus. Donec rhoncus felis velit, sed placerat nunc pellentesque elementum==",
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce lorem dui, elementum et odio ut, sollicitudin gravida dui. Ut nec dapibus elit. Morbi sit amet blandit justo. Quisque accumsan turpis metus, vel sodales nisl porta vel. Nunc placerat, mi id eleifend pharetra, massa urna imperdiet arcu, vitae dignissim nulla ex sed felis. Maecenas auctor risus ac nisl efficitur, sit amet euismod odio finibus. Praesent lacinia nunc id ex tempor, sed tempor nisl porta. Integer semper efficitur arcu, a dictum quam vulputate tempus. Nulla congue dapibus tortor vel volutpat. Curabitur et sollicitudin purus. Mauris quis aliquam augue. Nulla ac leo tristique, ultricies mauris sit amet, ultrices quam. Praesent placerat a lectus sit amet dapibus. Donec rhoncus felis velit, sed placerat nunc pellentesque elementum"
                ],
                "key_ops": [
                    "verify",
                    "wrapKey",
                    "encrypt"
                ],
                "alg": "RS256",
                "n": 
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce lorem dui, elementum et odio ut, sollicitudin gravida dui. Ut nec dapibus elit. Morbi sit amet blandit justo. Quisque accumsan turpis metus, vel sodales nisl porta vel. Nunc placerat, mi id eleifend pharetra, massa urna imperdiet arcu, vitae dignissim nulla"
            }
        ]
    }');
    END;

    With that new ORDS endpoint, we (temporarily) recreated the ORDS JWT Profile with the new ORDS API as a proxy for our true JWK URL. Like this (this JWK URL is entirely fake, BTW):

    BEGIN
      ORDS_SECURITY.CREATE_JWT_PROFILE(
          p_issuer => 'https://identity.oraclecloud.com/',
          p_audience => 'my_primary_audience' ,
          p_jwk_url => 'https://abcdefg-myadb.region-01.oraclecloudapps.com/ords/admin/jwk/jwk'
      );
      COMMIT;
    END;
    /

    Hypothetically if you were to navigate to that ORDS URI, you’d see a facsimile of the actual JWK information:

    So now, we’ve temporarily removed the JWK URL from the troubleshooting equation. However, we can still access that protected endpoint since we have a valid token and keys to decode it (courtesy of the ORDS endpoint we created)!

    And this my friends, is the type of mad scientist stuff that I live for. I had to share our experience to underscore two main points:

    1. You have a lot of flexibility with these ORDS APIs, and
    2. When it comes to troubleshooting/root cause analysis, you must break things down into individual parts!

    And that’s it for today. Class dismissed 🤓

    BTW, I took some inspiration from here too. And of course, a link to the JWT section of our docs.

    If you liked this, consider sharing. And if you have any cool or clever ORDS tricks you use, comment below!

    Follow

    And don’t forget to follow, like, subscribe, share, taunt, troll, or stalk me!

  • Configuring OCI IAM Domain JWTs to use with ORDS OAuth2.0 protected APIs

    Configuring OCI IAM Domain JWTs to use with ORDS OAuth2.0 protected APIs

    Table of Contents

    Intended Audience/Purpose

    Does this describe you?

    If this describes you, then keep reading!


    Creating a Domain

    Assuming you are at the central OCI Console, click the navigation icon at the top of your screen. Then, navigate to Identity & Security and click Create domain.

    NOTE: Make sure you are in your correct Compartment (this will be up to you to decide)! Everything I show here is done within the same /ords Compartment. 

    In this example, I’ve chosen to create an “External User” Domain type. I haven’t tested with any other Domain types, but I assume they all work similarly.

    Once you’ve entered all the information (e.g., Domain administrator values and Compartment), click Create Domain. Then, click on the Domain you just created. Next, you’ll need to create and add an “Integrated application.”

    Mini-tutorial: Adding a user to your Domain

    In this mini-tutorial, I demonstrate how to create a new user. Notice how I am using a personal email account, you can totally do this!

    Once you’ve added this user, they recieve a password reset email for this Domain. They must create a new password before logging in.

    Configure client access

    First, navigate to your domain, then select Settings.

    If this Configure client access box is unchecked, check it, then select Save Changes (the button at the bottom of the screen).

    That’s it. You’re done!

    Adding an Integrated Application to the Domain

    Adding the application

    From within your Domain dashboard, click the Integrated Applications menu item. The “Add application” modal will appear. Read through the application types and choose the most appropriate one. For this demo, I selected “Confidential Application.”

    Continue filling in the modal fields. Make sure you choose a unique name!

    I’ve left the URLs section empty for this demo and kept the Display settings checked. The Display settings are options for users when logging into their Domain (i.e., they don’t impact this workflow, but they are nice to have set for later).

    Click Next to continue to the Resource server configuration section.

    Configuring OAuth2.0

    In this section, you’ll configure your Resouce server and Client. First, we’ll create a Scope1.

    Resource server configuration

    When you configure your Scope, you must include a Primary audience field. Here, I’m just choosing the Domain’s name. The Primary audience needs to be structured exactly like this (with the trailing backslash)! Next, create a Scope (I’m using dot notation, since this will mirror the privilege I’ll create in ORDS, later).

    When finished, click Add.

    Client Configuration

    Next, you’ll include details about your client. In this demo, I use Postman as a client application stand-in. For this example I’m using the Authorization code grant type2, along with a Redirect URL.

    FYI: Postman has a /callback URL that you can use as a Redirect URL.
    Mini-tutorial: How do I find Postman’s Callback URL?

    From the Authorization tab, select OAuth 2.0. Then scroll down to the Configure New Token section. There you will find the Callback URL. Copy it. You’ll need it!

    I’ve chosen “All” for the Token issuance policy. Next, click Finish.

    Issuing a POST request to obtain a JWT

    You can send off that initial POST request to obtain the JWT from IAM with your Domain set. You’ll then use the details contained in that JWT to configure ORDS.

    Gathering the required values for the request

    If your application hasn’t already been activated, do so now.

    Next, click the Edit OAuth configuration button and retrieve your Client ID and Client secret. Save it to a clipboard or your environment files (if using this for an application).

    You will also need the Domain URL. Navigate to your Domain’s main dashboard (❗️make sure you are still in the correct Compartment). Then copy the Domain URL to your clipboard, or environment file (aka .env file).

    Setting up Postman

    In Postman, make sure you have selected “Request Headers” in the Add authorization data to field. Also, add the word “Bearer” to the Header Prefix field.

    You’ll want to select Authorization Code as the Grant Type. For the Authorization URL and Access Token URL, you will use your Domain URL followed by:

    • Auth URL
      [Your Domain URL]/oauth2/v1/authorize
    • Access Token URL
      [Your Domain URL]/oauth2/v1/token

    Next, add your Client ID, Client Secret, and Scope. Notice how the Scope in Postman uses the Primary audience and the Scope (if you don’t remember where these came from, review the Resource server configuration section). Select “Send as Basic Auth header” for the Client Authentication field.

    Requesting the JWT

    This next part is easy. From Postman, scroll down to the bottom of the Authorization window until you see the Get New Access Token button. Click it; a new browser window will appear. Enter the user credentials (I’m using the one I created specifically for this demo), and if any pop-up blocker notifications appear, make sure you “allow pop-ups.”

    Once authenticated with IAM, you’ll be redirected back to Postman (remember, this is the Redirect URL you added in the Client Configuration section).

    Copy the entire JWT, and then click Use Token. You’ll now see the token in the Current Token field in Postman.

    HELP ME! Okay, if the Sign-in and/or Redirect didn't work for you, it might be that you are still signed into another OCI session, with a different user (a user that doesn't belong to this Domain). So, what I've had to do in the past is make sure I'm logged out of all OCI sessions, clear my cookies, close out my browser, and then restart with a new one. This has nothing to do with ORDS, but its what worked for me.

    ORDS configuration

    In these following sections, you will configure your ORDS JWT Profile and ORDS Privilege (associated with the Scope you’ve created). But first, you’ll need to decode your JWT.

    Decoding the JWT

    In the past, I’ve used two different tools for decoding these JWTs: JWT.io and JWT.ms. In this demonstration I’m using JWT.io.

    Paste your JWT into one of the decoders and copy the following values to your clipboard:

    • iss
    • scope
    • aud

    The values you’ll need:

    Creating the ORDS JWT profile

    Step one: Create your JWT Profile. This process “registers” a JWT profile with ORDS, so when an incoming request comes in, ORDS can “cross-reference” information related to Issuers, Audiences, and Scopes.

    The easiest way to do all this is to first navigate to an SQL Worksheet and then select the schema from the Navigator tab. Then, under the database objects drop-down menu, choose Packages.

    Click the OAUTH package to expand it, scroll to the bottom of that list, right click the CREATE_JWT_PROFILE PL/SQL procedure, and click Run. A slider will appear.

    Enter your details exactly like you see below:

    • P_ISSUER is https://identity.oraclecloud.com
    • P_AUDIENCE should be whatever your Primary audience is, with the trailing slash included!
    • P_JWK_URL
    FYI: The P_JWK_URL is a combination of [Your Domain URL] + /admin/v1/SigningCert/jwk (this is the endpoint identified in the OCI IAM Domains API doc). And since this is a /v1 endpoint, I assume there may be more. So, double-check the docs in case of a different/updated version. 

    Then, execute the PL/SQL procedure.

    Next, you’ll create an ORDS Privilege (to associate with our protected ORDS Resources).

    Creating an ORDS privilege

    Aka, the “thing” we are protecting and making accessible with a valid JWT. Navigate to your REST Workshop. From the Security tab, select Privileges.

    Create a new Privilege and name it exactly the same as the Scope you created in IAM. DO NOT INCLUDE the Primary audience in this scope name (In fact, you can’t; we won’t let you anyway 🤣)! In the Protected Modules tab, select the Module you want to protect. Either drag it from the Available Modules tab to the Selected Modules tab or use the arrows to move it over. Then click Create.

    A peek at the ORDS API I’m using:

    This just prints out the Current User, using the ORDS :current_user Implicit parameter.

    Accessing your protected Resource (aka ORDS API) with the JWT

    Now, you may access this protected resource. Unless you’ve taken an hour to set ORDS up, your token will still be valid (it’s valid for an hour). Otherwise, you’ll need to obtain a new, fresh JWT. Using the target ORDS API (mine is the “Hello :current_user” endpoint) in your Postman request field, click Send. You’ll see two things happen (if you are viewing your Postman Console):

    1. An initial POST request to the Token server, and
    2. A subsequent GET request to your target ORDS API

    I should clarify…you might not see that initial POST request because you would have accomplished this before you opened the Postman Console. So, if you’d like to see these requests in real time, do this:

    • Clear out your cookies
    • Delete your existing token
    • Clear out the Console
    • Open the Console back up
    • Request a new JWT
    • Request your ORDS API (your protected Resource)

    The End

    And that’s it, finished! If you’ve made it this far, congratulations. That was a lot of work. Time for a coffee break!

    Follow

    And don’t forget to follow, like, subscribe, share, taunt, troll, or stalk me!

    1. Read up on Scopes here. ↩︎
    2. There are four different Grant Types in the OAuth 2.0 framework: Authorization Code, Implicit, Resource Owner Password Credentials, and Client Credentials. You can read more about them here. ↩︎