<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Patrick Guha on Medium]]></title>
        <description><![CDATA[Stories by Patrick Guha on Medium]]></description>
        <link>https://medium.com/@p.ironspur?source=rss-8bdf41c123c2------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*EwG2gG2uuj24wjtpPE1zhg@2x.jpeg</url>
            <title>Stories by Patrick Guha on Medium</title>
            <link>https://medium.com/@p.ironspur?source=rss-8bdf41c123c2------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 31 May 2026 18:48:19 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@p.ironspur/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Four Steps for Startups to Master Visual Document Understanding with AI]]></title>
            <link>https://medium.com/google-cloud-for-startups/four-steps-for-startups-to-master-visual-document-understanding-with-ai-c25785722fb2?source=rss-8bdf41c123c2------2</link>
            <guid isPermaLink="false">https://medium.com/p/c25785722fb2</guid>
            <category><![CDATA[ocr]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[google-gemini]]></category>
            <dc:creator><![CDATA[Patrick Guha]]></dc:creator>
            <pubDate>Fri, 08 May 2026 18:40:11 GMT</pubDate>
            <atom:updated>2026-05-08T19:43:19.169Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ogu5yeBstizJpKEoWM1g5w.png" /></figure><p>AI agents are transforming the nature of work by automating complex workflows with speed, scale, and accuracy. For AI startups — whether you are building the next-generation AI assistant for legal professionals, automating digital mortgage and real estate closings, streamlining complex estate planning, or pioneering digital musculoskeletal health and physical therapy — processing complex, highly-formatted documents is at the core of your product.</p><p>Contracts, loan applications, and clinical records are rarely just plain text. They are densely packed with tables, signatures, charts, and highly specific spatial layouts. Traditionally, developers had to rely on brittle pipelines involving disparate optical character recognition (OCR) tools and text-only large language models (LLMs).</p><p>Today, native multimodal models have changed the game. Working closely with our DeepMind and Google Cloud AI teams, we’ve developed a simple four-step framework to help your startup build reliable, highly scalable, and economical document understanding systems using Gemini.</p><h3>Step #1: Skip the OCR pipeline and leverage dynamic tokenization</h3><p>For complex document processing, the golden standard is now direct inference through native multimodal models. Instead of preprocessing documents through a traditional OCR engine, pass the PDF pages directly to Gemini. Models like <a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/gemini/3-1-pro">Gemini 3.1 Pro</a>, <a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/gemini/3-flash">3 Flash</a>, and <a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/gemini/3-1-flash-lite">3.1 Flash-Lite</a> natively understand document layouts, tables, and embedded images.</p><p><strong>Optimize your PDF format:</strong> For text-heavy workloads like master service agreements or estate planning documents, try to ingest native PDFs (where text is rendered as text) rather than flat scanned images. This makes the text machine-readable, which is far easier for the model to edit, search, and manipulate.</p><p><strong>Mastering Gemini 3 Tokenization:</strong> With the Gemini 3 models, document tokenization uses a variable sequence length, replacing the older Pan and Scan method for better quality and latency. You can now tightly control costs and performance by explicitly setting the media_resolution for your PDF inputs:</p><ol><li><strong>UNSPECIFIED (560 tokens/page):</strong> The default setting. The token count for this level varies significantly between Gemini 3 and earlier Gemini models.</li><li><strong>LOW (280 tokens/page):</strong> Best for simple text extraction or global summaries.</li><li><strong>MEDIUM (560 tokens/page):</strong> A balance between detail, cost, and latency. Ideal for standard document extraction.</li><li><strong>HIGH (1120 tokens/page):</strong> Higher token count, providing more detail for the model to work with, at the expense of increased latency and cost. Reserve this for fine-grained detail on complex layouts, tiny serial numbers on hardware, or dense financial tables.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SeEh5lh4p34oo9XQVTkRkw.png" /></figure><p><em>Pro-tip:</em> You can set these resolutions at the top level of your generationConfig, or override them for individual media parts if you have a mixed batch of high-res charts and low-res text pages.</p><p><strong>Example</strong>: Set media_resolution per individual media part:</p><pre>response = client.models.generate_content(<br>  model=&quot;gemini-3.1-pro-preview&quot;,<br>  contents=[<br>      types.Part(<br>          file_data=types.FileData(<br>              file_uri=&quot;gs://cloud-samples-data/generative-ai/image/a-man-and-a-dog.png&quot;,<br>             mime_type=&quot;image/jpeg&quot;,<br>          ),<br>          media_resolution=types.PartMediaResolution(<br>              level=types.PartMediaResolutionLevel.MEDIA_RESOLUTION_HIGH  # High resolution<br>          ),<br>      ),<br>      Part(<br>          file_data=types.FileData(<br>             file_uri=&quot;cloud-samples-data/generative-ai/video/behind_the_scenes_pixel.mp4&quot;,<br>            mime_type=&quot;video/mp4&quot;,<br>          ),<br>          media_resolution=types.PartMediaResolution(<br>              level=types.PartMediaResolutionLevel.MEDIA_RESOLUTION_LOW  # Low resolution<br>          ),<br>      ),<br>      &quot;When does the image appear in the video? What is the context?&quot;,<br>  ],<br>)<br>print(response.text)</pre><p><strong>Example</strong>: Or set media_resolution globally (via GenerateContentConfig)</p><pre>response = client.models.generate_content(<br>  model=&quot;gemini-3.1-flash-lite&quot;,<br>  contents=[<br>      types.Part(<br>          file_data=types.FileData(<br>              file_uri=&quot;gs://cloud-samples-data/generative-ai/pdf/1706.03762v7.pdf&quot;,<br>              mime_type=&quot;application/pdf&quot;,<br>          ),<br>      ),<br>      &quot;Please summarize the given document for a general audience.&quot;,<br>  ],<br>  config=types.GenerateContentConfig(<br>media_resolution=types.MediaResolution.MEDIA_RESOLUTION_MEDIUM  # Global setting<br>  ),<br>)<br>print(response.text)</pre><h3>Step #2: Prompt engineering for spatial and mixed-media context</h3><p>Many startups, particularly innovative legal case management platforms, deal with documents that mix dense text with embedded images (like exhibits in legal PDFs). When reasoning over these mixed-media documents, send the full PDF pages <em>as-is</em> to Gemini. Because Gemini processes the entire page visually, it preserves the spatial relationship between the text and the images.</p><p>To get the most out of the model, adhere to these structural prompt best practices:</p><ul><li><strong>Order matters:</strong> If your request contains a single PDF, always place the PDF file <em>before</em> the text prompt in your API request.</li></ul><p><strong>Example</strong>: Same example as above with a special callout to the ordering of the PDF Part object first then text the prompt/instruction in the Contents message</p><pre>text_part = &quot;&quot;&quot;<br>You are a professional document summarization specialist. Please summarize the given document for a general audience.&quot;&quot;&quot;<br><br>response = client.models.generate_content(<br>  model=&quot;gemini-3.1-flash-lite&quot;,<br>  contents=[<br>      types.Part(<br>          file_data=types.FileData(<br>              file_uri=&quot;gs://cloud-samples-data/generative-ai/pdf/1706.03762v7.pdf&quot;,<br>              mime_type=&quot;application/pdf&quot;,<br>          ),<br>      ),<br>      text_part,<br>  ],<br>  config=types.GenerateContentConfig(<br>media_resolution=types.MediaResolution.MEDIA_RESOLUTION_MEDIUM  # Global setting<br>  ),<br>)<br>print(response.text)</pre><ul><li><strong>Split massive files:</strong> If you are dealing with a long document (like a massive mortgage closing packet), consider splitting it into multiple smaller PDFs before processing it to improve retrieval accuracy and parsing speed. There are great tools out there powered by Gemini, such as <a href="https://developers.googleblog.com/build-a-smart-financial-assistant-with-llamaparse-and-gemini-31/">LlamaParse</a>, that can help you perform <a href="https://developers.llamaindex.ai/llamaparse/split/">splitting</a> and other tasks. It is also best practice to rotate pages to the correct orientation before uploading.</li></ul><p><strong>Example</strong>: Create a split job with category definitions with LlamaParse</p><pre>job = client.beta.split.create(<br>    document_input={<br>        &quot;type&quot;: &quot;file_id&quot;,<br>        &quot;value&quot;: file_id,<br>    },<br>    categories=[<br>        {<br>            &quot;name&quot;: &quot;essay&quot;,<br>            &quot;description&quot;: &quot;A philosophical or reflective piece of writing that presents personal viewpoints, arguments, or thoughts on a topic without strict formal structure&quot;,<br>        },<br>        {<br>            &quot;name&quot;: &quot;research_paper&quot;,<br>            &quot;description&quot;: &quot;A formal academic document presenting original research, methodology, experiments, results, and conclusions with citations and references&quot;,<br>        },<br>    ],<br>)<br><br>print(f&quot;✅ Split job created: {job.id}&quot;)<br>print(f&quot;   Status: {job.status}&quot;)</pre><ul><li><strong>Part isolation:</strong> For multi-document reasoning across a corpus, structure your requests so each document is a separate Part in the prompt with clear document identifiers.</li></ul><p><strong>Example</strong>: Interleaving document identifiers inside the prompt of the multi-part Content message with PDFs as unique Parts.</p><pre>response = client.models.generate_content(<br>    model=&quot;gemini-3.1-flash-lite&quot;,<br>    contents=[<br>        &quot;Document 1: Master Service Agreement&quot;,<br>        types.Part(<br>            file_data=types.FileData(<br>                file_uri=&quot;gs://&lt;SAMPLE-BUCKET&gt;/msa_doc.pdf&quot;,<br>                mime_type=&quot;application/pdf&quot;,<br>            ),<br>            # Apply medium resolution specifically to Document 1<br>            media_resolution=types.PartMediaResolution(<br>                level=types.PartMediaResolutionLevel.MEDIA_RESOLUTION_MEDIUM<br>            )<br>        ),<br>        &quot;Document 2: Statement of Work&quot;,<br>        types.Part(<br>            file_data=types.FileData(<br>                file_uri=&quot;gs://&lt;SAMPLE-BUCKET&gt;/sow_doc.pdf&quot;,<br>                mime_type=&quot;application/pdf&quot;,<br>            ),<br>            # Apply medium resolution specifically to Document 2<br>            media_resolution=types.PartMediaResolution(<br>                level=types.PartMediaResolutionLevel.MEDIA_RESOLUTION_MEDIUM<br>            )<br>        ),<br>        &quot;Analyze both documents. Compare the liability clauses in Document 1 with the deliverables outlined in Document 2.&quot;<br>    ]<br>)<br>print(response.text)</pre><h3>Step #3: Build dynamic routing and mitigate model limitations</h3><p>When building multi-agent systems (like those built on Google’s <a href="https://docs.cloud.google.com/agent-builder/agent-development-kit/overview">Agent Development Kit</a>), give your agent the ability to dynamically route tasks. We recommend setting up two distinct tools:</p><ul><li><strong>General RAG (Text-based retrieval):</strong> Standard vector search over extracted textual data.</li><li><strong>Multimodal RAG (Image-based retrieval):</strong> Sending relevant raw pages/images to Gemini for visual analysis.</li></ul><p>Instruct your agent’s system prompt to use the image retrieval tool when the user’s query involves visual content (e.g., charts, signatures, or scanned forms). Think of this as an on-demand visual analysis tool.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pchvjB5yreVl_MPeRgG4gQ.jpeg" /></figure><p><strong>Designing for limitations:</strong> It is crucial to build your workflows with the model’s limitations in mind. While multimodal models are incredibly powerful, they are not perfect spatial calculators.</p><ul><li><strong>Approximations:</strong> The models are not precise at locating exact coordinates of text/objects and might only return approximated counts of objects on a page.</li><li><strong>Handwriting:</strong> Be aware that models may hallucinate when interpreting messy handwritten text in PDFs (a common challenge for startups processing intake clinical notes or physical therapy records). Design your agent to flag highly ambiguous handwriting for “human-in-the-loop” (HITL) review.</li><li><strong>Confidence Thresholding</strong>: Program your agent to return a “certainty score” alongside its extraction. For mission-critical data (like financial totals or medical IDs), set a threshold (e.g., 90%). If the model’s confidence is lower, route the task to a fallback OCR engine or a human-in-the-loop for verification.</li></ul><h3>Step #4: Optimize economics for massive scale</h3><p>At startup scale — processing tens of millions of pages per month — passing every single image to a full vision language model (VLM) during high-volume ingestion is economically unsustainable.</p><p>Here is our blueprint for massive, economical scale:</p><ul><li><strong>Adopt Multimodal Embeddings:</strong> Instead of sending every ingested image to Gemini for reasoning, send them to a multimodal embedding model such as <a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/gemini/embedding-2">Gemini Embedding 2</a>. You pay a fraction of a cent to make the image searchable. You only invoke the more expensive Gemini model <em>after</em> a search finds a relevant image.</li><li><strong>Use Gemini Flash-Lite for the heavy lifting:</strong> For initial extraction passes and OCR-ing simple tables, use Gemini 3.1 Flash-Lite. It has the cheapest per-token cost and is incredibly fast.</li><li><strong>Leverage Context Caching:</strong> If your users have standard reference documents (like massive legal codebases) that your agent queries repeatedly, turn on <a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/context-cache/context-cache-overview#explicit-caching">explicit context caching</a>. You pay to process the heavy document images once, and every subsequent query over the next hour is discounted by up to 90%.</li><li><strong>Routing Requests:</strong> For global applications, route your document processing requests to the nearest Google Cloud region by using the global endpoint. This minimizes the “time to first token” (TTFT) for your users and allows you to utilize regions with higher availability. You can also leverage lower pricing for non-urgent batch processing of documents by using <a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/capabilities/batch-prediction-gemini">batch inference</a> or <a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/flex-paygo">Flex PayGo</a>.</li></ul><h3>Ready to start building visually intelligent agents? Let’s get started!</h3><ul><li>Explore the <a href="https://cloud.google.com/resources/content/building-ai-agents">Startups technical guide: AI agents</a> to dive deeper into agent architectures.</li><li>Try out Gemini 3.1 Pro, 3 Flash and, 3.1 Flash-Lite in <a href="https://aistudio.google.com/">Google AI Studio</a> to test multimodal document capabilities.</li><li>Review the full <a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/capabilities/document-understanding">Gemini Document Understanding best practices</a>.</li><li>Gemini models can be configured to take advantage of GCP’s <a href="https://docs.cloud.google.com/vertex-ai/generative-ai/docs/vertex-ai-zero-data-retention">enterprise-grade security and compliance</a>, so your team can spend more time building secure solutions.</li><li>No matter where you are with AI adoption, we are here to help. Contact our Startup team today to learn how you can get up to $350,000 USD in cloud credits with the <a href="https://cloud.google.com/startup?e=48754805&amp;hl=en">Google for Startups Cloud Program</a>.</li></ul><p>Special thank you to my co-author, <a href="https://www.linkedin.com/in/nanditha-e-9b21a7a1/">Nanditha Embar</a>!</p><p><strong><em>Disclaimer:</em></strong></p><p>The views and opinions expressed in this article are those of the authors and do not necessarily reflect the official policy or position of Google Cloud. Recommendations noted here should be tested in a non-production environment before production deployment. Users are responsible for assessing the cost and security implications of their own deployments.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c25785722fb2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud-for-startups/four-steps-for-startups-to-master-visual-document-understanding-with-ai-c25785722fb2">Four Steps for Startups to Master Visual Document Understanding with AI</a> was originally published in <a href="https://medium.com/google-cloud-for-startups">Google Cloud for Startups</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Simplifying B2B integrations with AWS Step Functions Workflow Studio]]></title>
            <link>https://medium.com/@p.ironspur/simplifying-b2b-integrations-with-aws-step-functions-workflow-studio-b0d3d1a8657a?source=rss-8bdf41c123c2------2</link>
            <guid isPermaLink="false">https://medium.com/p/b0d3d1a8657a</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[b2b]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws-step-functions]]></category>
            <dc:creator><![CDATA[Patrick Guha]]></dc:creator>
            <pubDate>Mon, 04 Oct 2021 13:57:56 GMT</pubDate>
            <atom:updated>2021-10-04T15:00:57.594Z</atom:updated>
            <content:encoded><![CDATA[<p><em>This post is written by Patrick Guha, Associate Solutions Architect, WWPS, Hamdi Hmimy, Associate Solutions Architect, WWPS, and Madhu Bussa, Senior Solutions Architect, WWPS</em></p><p>B2B integration helps organizations with disparate technologies communicate and exchange business-critical information. However, organizations typically have few options in building their B2B pipeline infrastructure. Often, they use an out-of-the-box SaaS integration platform that may not meet all of their technical requirements. Other times, they code an entire pipeline from scratch.</p><p><a href="https://aws.amazon.com/step-functions/">AWS Step Functions</a> offers a solution to this challenge. It provides both the code customizability of <a href="https://aws.amazon.com/lambda/">AWS Lambda</a> with the low-code, visual building experience of Workflow Studio.</p><p>This post introduces a customizable serverless architecture for B2B integrations. It explains how the solution works and how it can save you time in building B2B integrations.</p><h3>Overview</h3><p>The following diagram illustrates components and interactions of the example application architecture:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*iqYJGEZgqAlvklly.jpg" /></figure><p>This section describes building a configurable B2B integration pipeline with AWS serverless technologies. It walks through the components and discusses the flow of transactions from trading partners to a consolidated database.</p><h3>Communication</h3><p>The B2B integration pipeline accepts transactions both in batch (a single file with many transactions) and in real-time (a single transaction) modes. Batch communication is over open standard SFTP protocol, and real-time communication is over the REST/HTTPS protocol.</p><p>For batch communication needs, <a href="https://aws.amazon.com/aws-transfer-family/">AWS Transfer Family</a> provides managed support for file transfers directly between <a href="https://aws.amazon.com/s3/">Amazon Simple Storage Service</a> (S3) or <a href="https://aws.amazon.com/efs/">Amazon Elastic File System</a> (EFS). EFS provides a serverless, elastic file system that lets you share file data without provisioning or managing storage.</p><p><a href="https://aws.amazon.com/eventbridge/">Amazon EventBridge</a> provides serverless event bus functionality to automate specific actions in the B2B pipeline. In this case, batch transaction uploads from a partner trigger the B2B pipeline. When a file is put in S3 from the Transfer SFTP server, EventBridge captures the event and routes to a target Lambda function.</p><p>As batch transactions are saved via AWS Transfer SFTP to Amazon S3, AWS CloudTrail captures the events. It provides the underlying API requests as files are PUT into S3, which triggers the EventBridge rule created previously.</p><p>For real-time communication needs, <a href="https://aws.amazon.com/api-gateway/">Amazon API Gateway</a> provides an API management layer, allowing you to manage the lifecycle of APIs. Trading partners can send their transactions to this API over the ubiquitous REST API protocol.</p><h3>Processing</h3><p><a href="https://aws.amazon.com/sqs/">Amazon Simple Queue Service</a> (SQS) is a fully managed queuing service that allows you to decouple applications. In this solution, SQS manages and stores messages to be processed individually.</p><p>Lambda is a fully managed serverless compute service that allows you to create business logic functions as needed. In this example, Lambda functions process the data from the pipeline to clean, format, and upload the transactions from SQS.</p><p>Step Functions manages the workflow of a B2B transaction. Step Functions is a low-code visual workflow service used to orchestrate AWS services, automate business processes, and build serverless applications. Workflows manage failures, retries, parallelization, service integrations, and observability so developers can focus on higher-value business logic.</p><p>API Gateway is used in the processing pipeline of the solution to enrich the transactions coming through the pipeline.</p><p><a href="https://aws.amazon.com/dynamodb/">Amazon DynamoDB</a> serves as the database for the solution. DynamoDB is a key-value and document database that can scale to virtually any number of requests. As the pipeline experiences a wide range of transaction throughputs, DynamoDB is a good solution to store processed transactions as they arrive.</p><h3>Batch transaction flow</h3><ol><li>A trading partner logs in to AWS Transfer SFTP and uploads a batch transaction file.</li><li>An S3 bucket is populated with the batch transaction file.</li><li>A CloudTrail data event captures the batch transaction file being PUT into S3.</li><li>An EventBridge rule is triggered from the CloudTrail data event.</li><li>Lambda is triggered from the EventBridge rule. It processes each message from the batch transaction file and sends individual messages to SQS.</li><li>SQS stores each message from the file as it is passed through from Lambda.</li><li>Lambda is triggered from each SQS incoming message, then invokes Step Functions to run through the following steps for each transaction.</li><li>Lambda accepts and formats the transaction payload.</li><li>API Gateway enriches the transaction.</li><li>DynamoDB stores the transaction.</li></ol><h3>Single/real-time transaction flow</h3><ol><li>A trading partner uploads a single transaction via an API Gateway REST API.</li><li>API Gateway sends a single transaction to Lambda SQS writer function via proxy integration.</li><li>SQS stores each message from the API POSTs.</li><li>Lambda is triggered from each SQS incoming message. It invokes Step Functions to run through the workflow for each transaction.</li><li>Lambda accepts and formats the transaction payload.</li><li>API Gateway enriches the transaction.</li><li>DynamoDB stores the transaction.</li></ol><h3>Exploring and testing the architecture</h3><p>To explore and test the architecture, there is an <a href="https://aws.amazon.com/serverless/sam/">AWS Serverless Application Model</a> (AWS SAM) template. The AWS SAM template creates an <a href="https://aws.amazon.com/cloudformation/">AWS CloudFormation</a> stack for you. This can help you save time building your own B2B pipeline, as you can deploy and customize the example application.</p><p>To deploy in your own AWS account:</p><ol><li>To install AWS SAM, visit the <a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html">installation page</a>.</li><li>To deploy the AWS SAM template, navigate to the directory where the template is located. Run the following bash commands in the terminal:</li></ol><pre>git clone https://github.com/aws-samples/simplified-serverless-b2b-application</pre><p>cd simplified-serverless-b2b-application</p><p>sam build</p><p>sam deploy --guided --capabilities CAPABILITY_NAMED_IAM</p><h3>Prerequisites</h3><ol><li>Create an SSH key pair. To authenticate to the AWS Transfer SFTP server and upload files, you must generate an SSH key pair. Once created, you must copy the contents of the public key to the <strong>SshPublicKeyParameter</strong> displayed after running the <strong>sam deploy</strong> command. Follow <a href="https://docs.aws.amazon.com/transfer/latest/userguide/key-management.html#sshkeygen">the instructions</a> to create an SSH key pair for Transfer.</li><li>Copy batch and real-time input. The following XML content contains multiple example transactions to be processed in the batch workflow. Create an XML file on your machine with the following content:</li></ol><pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</pre><pre>&lt;Transactions&gt;</pre><pre> &lt;Transaction TransactionID=&quot;1&quot;&gt;</pre><pre>  &lt;Notes&gt;Transaction made between user 57 and user 732.&lt;/Notes&gt;</pre><pre> &lt;/Transaction&gt;</pre><pre> &lt;Transaction TransactionID=&quot;2&quot;&gt;</pre><pre>  &lt;Notes&gt;Transaction made between user 9824 and user 2739.&lt;/Notes&gt;</pre><pre> &lt;/Transaction&gt;</pre><pre> &lt;Transaction TransactionID=&quot;3&quot;&gt;</pre><pre>  &lt;Notes&gt;Transaction made between user 126 and user 543.&lt;/Notes&gt;</pre><pre> &lt;/Transaction&gt;</pre><pre> &lt;Transaction TransactionID=&quot;4&quot;&gt;</pre><pre>  &lt;Notes&gt;Transaction made between user 5785 and user 839.&lt;/Notes&gt;</pre><pre> &lt;/Transaction&gt;</pre><pre> &lt;Transaction TransactionID=&quot;5&quot;&gt;</pre><pre>  &lt;Notes&gt;Transaction made between user 83782 and user 547.&lt;/Notes&gt;</pre><pre> &lt;/Transaction&gt;</pre><pre> &lt;Transaction TransactionID=&quot;6&quot;&gt;</pre><pre>  &lt;Notes&gt;Transaction made between user 64783 and user 1638.&lt;/Notes&gt;</pre><pre> &lt;/Transaction&gt;</pre><pre> &lt;Transaction TransactionID=&quot;7&quot;&gt;</pre><pre>  &lt;Notes&gt;Transaction made between user 785 and user 7493.&lt;/Notes&gt;</pre><pre> &lt;/Transaction&gt;</pre><pre> &lt;Transaction TransactionID=&quot;8&quot;&gt;</pre><pre>  &lt;Notes&gt;Transaction made between user 5473 and user 3829.&lt;/Notes&gt;</pre><pre> &lt;/Transaction&gt;</pre><pre> &lt;Transaction TransactionID=&quot;9&quot;&gt;</pre><pre>  &lt;Notes&gt;Transaction made between user 3474 and user 9372.&lt;/Notes&gt;</pre><pre> &lt;/Transaction&gt;</pre><pre> &lt;Transaction TransactionID=&quot;10&quot;&gt;</pre><pre>  &lt;Notes&gt;Transaction made between user 1537 and user 9473.&lt;/Notes&gt;</pre><pre> &lt;/Transaction&gt;</pre><pre> &lt;Transaction TransactionID=&quot;11&quot;&gt;</pre><pre>  &lt;Notes&gt;Transaction made between user 2837 and user 7383.&lt;/Notes&gt;</pre><pre> &lt;/Transaction&gt;</pre><pre>&lt;/Transactions&gt;</pre><ol><li>Similarly, the following content contains a single transaction to be processed in the real-time workflow.</li><li>transactionId=12&amp;transactionMessage= Transaction made between user 687 and user 329.</li><li>Download <a href="https://cyberduck.io/download/">Cyberduck</a>, an SFTP client, to upload the batch transaction file to the B2B pipeline.</li></ol><h3>Uploading the XML file to Transfer and POST to API Gateway</h3><p>Use Cyberduck to upload the batch transaction file to the B2B pipeline. Follow the <a href="https://docs.aws.amazon.com/transfer/latest/userguide/transfer-file.html">instructions here</a> to upload the preceding transactions XML file. You can find the Transfer server endpoint in both the Transfer console and the <em>Outputs</em> section of the AWS SAM template.</p><p>Use the API Gateway console to test the POST method for the single transaction workflow. Navigate to API Gateway in the AWS Management Console and choose the REST API created by the AWS SAM template called <em>SingleTransactionAPI</em>.</p><p>In the <em>Resources</em> pane, view the methods. Choose the POST method. Next, choose the client <em>Test</em> bar on the left.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ejP5hBjQX8_jahYt.png" /></figure><p>Copy the single real-time transaction into the <em>Query Strings</em> text box then choose<strong> Test</strong>. This sends the single transaction and starts the real-time workflow of the B2B pipeline.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*FkPSoIEmnkpjUk1n.png" /></figure><h3>Viewing Step Functions executions</h3><p>Navigate to the Step Functions console. Choose the state machine created by the AWS SAM template called <em>StepFunctionsStateMachine</em>.</p><p>In the <em>Executions</em> tab, you see a number of successful executions. Each transaction represents a Step Functions state machine execution. This means that every time a transaction is submitted by a trading partner to SQS, it is individually processed as a unique Step Functions state machine execution. This granularity is useful for tracking transactions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0W50vQ6Nmu9GIr5x.png" /></figure><h3>Viewing Workflow Studio</h3><p>Next, view the Step Functions state machine definition. On the <em>StepFunctionsStateMachine</em> page, choose <strong>Edit</strong>. You see a code and visual representation of your workflow.</p><p>The code version uses <a href="https://docs.aws.amazon.com/step-functions/latest/dg/concepts-amazon-states-language.html">Amazon States Language</a>, allowing you to modify the state machine as needed. Choose the <strong>Workflow Studio</strong> button to get a visual representation of the services and integrations in the workflow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wOjVoL_me_unPXcK.png" /></figure><p>The Workflow Studio helps you to save time while building a B2B pipeline. There are over 40 different actions you can take on various AWS services and flow states that can provide additional logic to the workflow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vFgZ3b5xk7s6q5bB.png" /></figure><p>One of the largest benefits of Workflow Studio are the time-savings possible through built-in integrations to AWS services. This architecture includes two integrations: API Gateway request and DynamoDB PutItem.</p><p>Choose the API Gateway request state in the diagram. To make a request to the API Gateway REST API, you update the API <em>Parameters</em> section in <em>Configuration</em>. Previously, you may have used a Lambda function to perform this action, adding extra code to maintain a B2B pipeline.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*D3Uk6nNJJivY_0od.png" /></figure><p>The same is true for DynamoDB. Choose the DynamoDB PutItem state to view more. The configuration to put the item is made in the API Parameters section. To connect to any other AWS services via <em>Actions</em>, add <a href="https://aws.amazon.com/iam/">Identity and Access Manager</a> (IAM) permissions for Step Functions to access them. These examples include the necessary IAM permissions for Step Functions to both API Gateway and DynamoDB in the AWS SAM template.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vCKeFl_HEZf9xHrJ.png" /></figure><h3>Cleaning up</h3><p>To avoid ongoing charges to your AWS account, remove the created resources:</p><ul><li>Use the CloudFormation console to delete the stack created as part of this demo. Choose the stack, choose <strong>Delete</strong>, and then choose <strong>Delete stack</strong>.</li><li>Navigate to the S3 console and both <strong>Empty</strong> then <strong>Delete</strong> S3 buckets created from the stack: [stackName]-cloudtrails3bucket-[uniqueId] and [stackName]-sftpservers3bucket-[uniqueId]</li><li>Navigate to the CloudWatch console and delete the following log groups created from the stack: /aws/lambda/IntakeSingleTransactionLambdaFunction, /aws/lambda/SingleQueueUploadLambda, and /aws/lambda/TriggerStepFunctionsLambdaFunction.</li></ul><h3>Conclusion</h3><p>This post shows an architecture to share your business data with your trading partners using API Gateway, AWS Transfer for SFTP, Lambda, and Step Functions. This architecture enables organizations to quickly on-board partners, build event-driven pipelines, and streamline business processes.</p><p>To learn more about B2B pipelines on AWS, read: <a href="https://aws.amazon.com/blogs/compute/integrating-b2b-using-event-notifications-with-amazon-sns/">https://aws.amazon.com/blogs/compute/integrating-b2b-using-event-notifications-with-amazon-sns/.</a></p><p>For more serverless learning resources, visit <a href="https://serverlessland.com">Serverless Land</a>.</p><p><em>Originally published at </em><a href="https://aws.amazon.com/blogs/compute/simplifying-b2b-integrations-with-aws-step-functions-workflow-studio/"><em>https://aws.amazon.com</em></a><em> on October 4, 2021.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b0d3d1a8657a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using Oracle Identity Cloud Service for SSO in a Python 3 Flask Web Application]]></title>
            <link>https://medium.com/@p.ironspur/using-oracle-identity-cloud-service-for-sso-in-a-python-3-flask-web-application-c71cc6b3fa03?source=rss-8bdf41c123c2------2</link>
            <guid isPermaLink="false">https://medium.com/p/c71cc6b3fa03</guid>
            <category><![CDATA[sso]]></category>
            <category><![CDATA[flask]]></category>
            <category><![CDATA[oauth2]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[oracle]]></category>
            <dc:creator><![CDATA[Patrick Guha]]></dc:creator>
            <pubDate>Fri, 29 May 2020 18:46:13 GMT</pubDate>
            <atom:updated>2026-04-23T19:20:43.236Z</atom:updated>
            <content:encoded><![CDATA[<h3>Using Oracle Identity Cloud Service for Authentication in a Python 3 Flask Web Application</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B6xg65a8U760RVYukCX4cQ.png" /></figure><h3>Goal:</h3><p>This article is meant to document the proof-of-concept experiment in using Oracle’s Identity Cloud Service (IDCS) for Single-Sign-On (SSO) authentication in a Python web application built using the Flask framework.</p><h3>Motivation:</h3><p>Oracle has released a software development kit (SDK) for using IDCS with Python before. However, the most recent SDK was written to work with the Django web framework, and it only supports the now-deprecated Python version 2.7. Flask is a much more lightweight and easy to stand-up framework than Django for beginners in creating simple web apps. It is personally the framework I am familiar with. Thus, I thought it would useful to integrate IDCS with Flask. Of course, it goes without saying that in this process, the upgrade to Python 3.8 would also make the security of web apps much better.</p><p><strong>Before we go into how to integrate the IDCS with a Flask app, let’s first understand what each of the components are.</strong></p><h3>What is IDCS:</h3><p>Oracle’s Identity Cloud Service is an Identity and Access Management (IAM) platform that provides identity management, SSO, and identity governance for apps both in the cloud and on-premises. A service like this makes it much easier to allow granular access to your applications, and the provided SDK makes developers’ jobs easier by not making them write code from scratch. The focus of IDCS is to give enterprise-level access and security to an organization.</p><h3>What is Flask:</h3><p>Flask is a Python micro-framework that is very lightweight and easy-to-use. It does not natively include many components such as form validation or file uploads. It instead, is highly extensible, meaning that one can very easily add the best external components to their application as they see fit. Flask is built on Werkzeug utility library, which is a toolkit for Web Server Gateway interface (WSGI) applications. Werkzeug simply realizes software objects for requests and responses. Flask’s core is also built on Jinja2, which is a web template engine (templates being static HTML pages).</p><p><strong>Now that we’ve gone over what the components are, let’s take a high level overview of how authentication works with IDCS and a generic web application.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/702/1*XD7wv-e6bT2z8lbCuCnK0w.png" /></figure><ol><li>The user requests a protected resource.</li><li>The authentication module uses the SDK to generate a request-authorization-code URL for Oracle Identity Cloud Service and send this URL as a redirect response to the web browser.</li><li>The web browser calls the URL.</li><li>The Oracle Identity Cloud Service Sign In page appears.</li><li>The user submits their Oracle Identity Cloud Service sign-in credentials.</li><li>After the user logs in successfully, Oracle Identity Cloud Service creates a session for the user and issues an authorization code.</li><li>The web application makes a back-end (or server-to-server) call to exchange the authorization code for a user access token.</li><li>Oracle Identity Cloud Service issues the access token.</li><li>A session is established, and the user is redirected to the Home page.</li><li>The Home page of the web application appears.</li></ol><p>The above steps describe what it is called a “three-legged authentication flow” with the three coming from each party involved (user, web app, and IDCS.) The three-legged authentication flow, as you can see from step 6, uses the authorization code grant type.</p><p><strong>Let’s now look at the required setup you will need to do in IDCS to add a web application for SSO. *This requires an active IDCS account.*</strong></p><h3>IDCS Setup:</h3><ol><li>Login to the IDCS console.</li><li>In the console, expand the <strong>Navigation Drawer, </strong>click <strong>Applications</strong>.</li><li>In the <strong>Applications</strong> page, click <strong>Add</strong>.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/763/1*iMjHF6c8fZCWqVF73xXd2g.png" /></figure><p>4. In the <strong>Add Application </strong>chooser dialog, click <strong>Confidential Application</strong>.</p><p>5. Populate the <strong>Details </strong>pane as follows, and then click <strong>Next</strong>.</p><ul><li><strong>Name</strong>: SDK Web Application</li><li><strong>Description</strong>: SDK Web Application</li></ul><p>6. In the <strong>Client </strong>pane, select <strong>Configure this application as a client now</strong>, and. then populate the fields of this pane, as follows:</p><ul><li><strong>Allowed Grant Types</strong>: Select <strong>Client Credentials</strong> and <strong>Authorization Code</strong>.</li><li><strong>Allow non-HTTPS URLs</strong>: Select this check box. The sample application works in non-HTTPS mode.</li><li><strong>Redirect URL</strong>: <a href="http://localhost:8000/callback">http://localhost:8000/callback</a></li><li><strong>Post Logout Redirect URL</strong>: <a href="http://localhost:8000">http://localhost:8000</a></li></ul><p>7. In the <strong>Client</strong> pane, scroll down, click the <strong>Add</strong> button below <strong>Grant the client access to Identity Cloud Service Admin APIs.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/746/1*vkuzOOF8T99oVIIi16DCsQ.png" /></figure><p>8. In the <strong>Add App Role</strong> dialog window, select <strong>Authenticator Client</strong> and <strong>Me</strong> in the list, and then click <strong>Add</strong>.</p><p>9. Click <strong>Next</strong> in the <strong>Client</strong> pane and in the following panes until you reach the last pane. Then click <strong>Finish</strong>.</p><p>10. In the <strong>Application Added </strong>dialog box, make a note of the <strong>Client ID </strong>and <strong>Client Secret</strong> values (because your web application needs these values to integrate with Oracle Identity Cloud Service), and then click <strong>Close</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/691/1*8z6QunFZ_KChGpYLflQYeQ.png" /></figure><p>11. To activate the application, click <strong>Activate</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u6XVz2vWslY0VcLWdU321Q.png" /></figure><p>12. In the <strong>Activate Application?</strong> dialog box, click <strong>Activate Application</strong>. The success message <strong>The SDK Web Application application has been activated </strong>should<strong> </strong>appear.</p><p>13. In the IDCS console, click the user name at the top-right of the screen, and click <strong>Sign Out</strong>.</p><p><strong>Ok, now that we’ve configured IDCS, let’s jump into the code for our application. This is a very simplistic HTML web app meant to demonstrate the process. We will look at both the project structure and the individual files in the project.</strong></p><h3>Project Structure:</h3><h4>Constants.py</h4><p>Part 1 of 2 of the IDCS Python SDK. I included it in my GitHub repository, so there is no need to download it again from the IDCS console. This file simply holds constant variables for the main IdcsClient.py. This code is a bit long to post here, so refer to the GitHub.</p><h4>IdcsClient.py</h4><p>Part 2 of 2 of the IDCS Python SDK. This file is the critical client that connects to IDCS. I made no changes to this file. I simply found out how to use it in the Flask framework, rather than Django. This code is a bit long to post here, so refer to the GitHub here, as well.</p><h4>config.json</h4><p>Holds JSON variable that’s loaded with Oracle Identity Cloud Service connection information. Input the Client ID, Client Secret, and other details in <strong>bold</strong> here here:</p><pre>{  <br><strong>&quot;ClientId&quot; : &quot;your-client-id&quot;,  <br>&quot;ClientSecret&quot; : &quot;your-client-secret&quot;,  <br>&quot;BaseUrl&quot; : &quot;your-base-url&quot;,  <br>&quot;AudienceServiceUrl&quot; : &quot;your-audience-service-url (same as base url)&quot;</strong>,  &quot;scope&quot; : &quot;urn:opc:idm:t.user.me openid&quot;,  &quot;TokenIssuer&quot; : &quot;https://identity.oraclecloud.com/&quot;,  <br><strong>&quot;redirectURL&quot;: &quot;http://localhost:8000/home&quot;,  &quot;logoutSufix&quot;:&quot;/oauth2/v1/userlogout&quot;</strong>,  <br>&quot;LogLevel&quot;:&quot;INFO&quot;,  <br>&quot;ConsoleLog&quot;:&quot;True&quot;<br>}</pre><h4>main.py</h4><p>Main Flask web application. I’ll dive deep into this in a later section, since this is the crucial part of this experiment.</p><h4>requirements.txt</h4><p>Simple list of requirement libraries needed to run the IdcsClient.py and main.py.</p><pre>flask<br>requests<br>jwt<br>six<br>cryptography<br>simplejson<br>lru-ttl</pre><p>To install these Python packages, run:</p><pre>$ pip install -r requirements.txt</pre><h4>templates → login.html &amp; home.html</h4><p>HTML static templates that Flask renders. These define what our app looks like. Flask requires the HTML templates to be in the folder named <strong>templates</strong>. These templates also connect to main.py, so I’ll go over them in more depth in a later section.</p><h3>main.py:</h3><p>The main.py file has many key chunks of code that I’d like to go over here. These explanations will help describe both how IDCS and the Flask framework operate.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/327a19ae734afa198afeeae4472a033a/href">https://medium.com/media/327a19ae734afa198afeeae4472a033a/href</a></iframe><h4>Flask Basics</h4><p>If you are unfamiliar with Flask, the first thing you have to do is create a Flask object called <strong>app </strong>on line 6. To run the <strong>app</strong>, you simply write <strong>app.run</strong> on the port you want to serve the web app on (shown in line 97–98).</p><p>You’ll also see <strong>@app.route</strong> pop up a lot. In Flask, these <strong>app.routes</strong> define the pages of your web application. For example, you see that the app.route <strong>/home </strong>ties to the Python function <strong>home() </strong>on line 63–64. So, when someone goes to your-website.com/home, the <strong>home() </strong>code gets invoked.<strong> home()</strong> will render the <strong>home.html</strong> template code that we wrote. If there is not an <strong>@app.route</strong> above a Python function definition, then it is not accessible as a web page; it is only addressable in the code (example is <strong>getoptions()</strong> function on line 32).</p><h4>Flask and Sessions</h4><p>The last basic Flask concepts that I want to mention are <strong>app.secret_key</strong> and <strong>session</strong>. Sessions encompass a key difference between how IDCS interacts with Flask versus how it interacts with Django. <strong>app.secret_key</strong> is a required token (in production please make it more random than ‘secret’ like in line 9) that must be used to enable sessions in Flask. This key makes sure the <strong>session</strong> is cryptographically signed to avoid insecurity. A <strong>session</strong> object in Flask is used to hold global variables for a web application. It works like a Python dictionary, but it can used to track variable modifications. Session data in the Flask app is stored in the browser as a cookie.</p><p>Now let’s go back to how sessions are different in Flask and Django. Sessions in the Flask framework are <strong>client-side </strong>(stored on user’s local machine)<strong> </strong>by default, while sessions in Django are <strong>server-side </strong>(stored on cloud server machine). This affects IDCS integration greatly, and I’ll describe how with this example. The documentation for IDCS and Django integration show how to access an IDCS session variable such as, <strong>id_token </strong>(aka user access token from the three-legged authentication flow), by simply calling it like this:</p><pre>request.session[&#39;id_token&#39;] = id_token</pre><p>Because the <strong>id_token</strong> is stored on a secure IDCS <strong>server</strong> in the cloud, and Django sessions are <strong>server-side</strong>, they could easily get the <strong>id_token</strong> by just calling the session variable. If you were to try and access the session variable, <strong>id_token</strong>, in Flask the same way, you would get an error, because the Flask (<strong>client-side</strong>) session is on your local machine, and it has no idea what an <strong>id_token</strong> is. Therefore, the Flask web app must get the <strong>id_token </strong>by calling the IDCS REST API (I will describe how to do this in a later section).</p><ul><li><strong>Note</strong>: because sensitive information (<strong>id_token</strong>) is being sent over the internet via a REST API call, it is imperative that this connection be secure and resistant to man-in-the-middle attacks in production environments. Namely, you should be using the HTTPS protocol which involves adding an SSL/TLS (1.2/1.3) certificate to your web server (Apache, NGNIX, etc.) that the Flask app will be hosted on. I will leave it at that since it is beyond the scope of this experiment, but it still worth while bringing up.</li></ul><p><strong>Now that I’ve gone over the basics of Flask apps, routes, and sessions, I’ll go into each @app.route code more closely.</strong></p><h4>@app.route (‘/’)</h4><p>Staring on line 58 of main.py, this is the base page that users go to when they visit your Flask web app online. It simply renders the HTML template login.html shown here:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ecca3f73e0a91ae9ae421cffb492ecef/href">https://medium.com/media/ecca3f73e0a91ae9ae421cffb492ecef/href</a></iframe><p>The key code to look at is in line 30. This HREF tag points to the next important <strong>@app.route, /auth.</strong></p><h4>@app.route (‘/auth’)</h4><p>This route starts on line 13 and runs the <strong>auth() </strong>function. This function is responsible for authenticating the user to IDCS. It directly interfaces with the IDCS SDK (aka <strong>IdcsClient</strong>) and uses the <strong>getoptions() </strong>function to unpack the required credentials and lets the SDK use them. You might be wondering how get from <strong>/auth </strong>to our actual web app home page<strong> /home </strong>now. This is actually done by IDCS in the form of a redirect URL. In the <strong>config.json</strong> file from earlier, we defined the redirect URL to be <strong>“http://localhost:8000/home&quot;. </strong>This URL explicitly takes us back to the <strong>/home</strong> route of our app once IDCS finishes authenticating the user.</p><h4>@app.route (‘/home’)</h4><p>This route starts on line 63 and runs the <strong>home() </strong>function. This function is responsible for 1) rendering the <strong>home.html</strong> template and 2) calling the IDCS REST API for the authenticated user’s <strong>id_token</strong> I spoke about earlier. Since 1) is very similar to what I showed in the <strong>login.html</strong>, I’ll avoid talking about it again here. The more interesting responsibility is 2). The goal of calling the IDCS REST API here is to add the <strong>id_token</strong> to the Flask local client-session, or browser cookie. This <strong>id_token</strong> is needed to perform IDCS tasks. In our case, it will be needed to logout successfully.</p><p>Now, let’s look at the anatomy of calling the IDCS REST API in Python. First, it calls the <strong>getoptions()</strong> function to load the IDCS credentials. Next, on line 74, you can see that we run this code:</p><pre>session[&#39;code&#39;] = request.args.get(&#39;code&#39;)</pre><p>This line uses both creates a dictionary entry in the Flask session called ‘code’ and initializes it with the query parameter ‘code’ from the current URL. That’s a loaded statement, so let’s break it down. The Python library, request, is used here to parse URLs, and the argument it wants to take from the URL is ‘code’. ‘Code’ is actually the authorization code granted to the user once it passes authentication from IDCS in the <strong>/auth</strong> route. It is tacked onto the end URL of the session after passing. It is what’s known as a query parameter (aka whatever shows up after the “?” in a URL). Here’s a good diagram showing that here:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/653/1*eobnQg0vH0S3gbmKvFX5_Q.png" /></figure><p>Next, on line 76, we set the specific fields needed to make the correct call to the IDCS REST API endpoint. These fields are enclosed in a simple JSON object called <strong>data</strong>. We essentially do the same thing for the <strong>headers</strong> needed on line 82. The actual call to the IDCS endpoint is performed through the Python library, requests, and shown again here on line 86:</p><pre>response = requests.post(options[&#39;BaseUrl&#39;] + &#39;/oauth2/v1/token?&#39;, data=data, headers=headers, auth=(options[&#39;ClientId&#39;], options[&#39;ClientSecret&#39;]))</pre><p>The requests library is not be confused with the ‘request’ library used earlier. Request is a part of the Flask framework, while requests is an external library used to make REST calls with Python. Through a POST call to the IDCS endpoint ‘<strong>/oauth2/v1/token’</strong>, we will get back the user’s<strong> id_token</strong>, or access token. I highly recommend using the Python requests library as I did here. You could try making the call on your own through a similar cURL library, but I always ended up receiving an HTTP error code of 40x, meaning that something was wrong with the way I was calling it. Letting the requests library handle the authentication through <strong>auth=(options[‘ClientId’], options[‘ClientSecret’]))</strong> is a much easier solution.</p><p>Once you finally make the successful REST call, the resulting <strong>id_token</strong> will be sent back to you in the JSON object we call <strong>response</strong>. Line 89 simply parses that <strong>response</strong> JSON object and puts the <strong>id_token</strong> field into our local Flask session (or browser cookie), which we will use to logout successfully.</p><p>Line 91 highlights an important security feature this code implements through highlighting an interesting user edge case.</p><pre>if str(response.status_code) != &quot;200&quot;:        <br>   return render_template(&#39;login.html&#39;)</pre><p>If a user were to try and access our web app by purposely bypassing the base <strong>/</strong> route or even the <strong>/auth</strong> route (ex: going straight to <a href="http://my-web-app/home)">http://my-web-app/home</a>), this if statement blocks them from accessing our protected web app (<strong>home.html</strong>) without being expressly authenticated. Remember when I stated that I kept receiving an HTTP error code 40x when unsuccessfully trying to call the IDCS API? Well, that turned out to be a useful excercise. If a user goes straight to the <strong>/home</strong> route, the session variable ‘code’ (which can only be <strong>correctly</strong> set after successfully going through the <strong>/auth</strong> route) will be null. The IDCS API call requires ‘code’ as a parameter, so a would-be hacker will always get a failed HTTP error code 40x from the API as a response.</p><p>One might be thinking that they could simply spoof or fake an authentication code by giving a dummy ‘code’ query parameter (ex: <a href="http://my-web-app/home?code=spoof">http://my-web-app/home?code=spoof</a>). This would also block the hacker, because, again, the HTTP response code returned by the API would be 40x, not the success code 200. Only the exact authentication code can be used to return the user access token. Any response code other than 200 results in the user being redirected to the <strong>login.html</strong> page, where they would be forced to authenticate with IDCS. If the response code is 200, the user can finally see the protected web app, <strong>home.html.</strong></p><h4>@app.route(‘/logout’)</h4><p>Once the user of the web app is finished with their session, they will click the logout button on the HTML page, which is backed by an HREF tag pointing to the <strong>logout()</strong> function (similar to what we saw in the login() function earlier). The <strong>logout()</strong> function is key to keeping our app secure, as well. We’ll go over how it does so here.</p><p>The first thing we do on line 42 is grab the <strong>id_token</strong> we stored in our session in the <strong>/home</strong> route. Then, like usual, we <strong>getoptions()</strong> for configurations. We then start appending on each of those variables to a string called <strong>url</strong>. The final variable, <strong>id_token, </strong>is the last query parameter, <strong>token_hint, </strong>in the <strong>url</strong>. This long <strong>url</strong> is the redirect url to IDCS, which handles ending the session on the server. We also clear all of our local session variables in Flask by running <strong>session.clear()</strong>. Lastly on line 55, we redirect our browser to the <strong>url </strong>we set, thus ending our secure web session from Flask to IDCS.</p><h3>Closing Remarks</h3><p>I hope this code overview and process flow was useful in explaining the inner workings of both the Oracle Identity Cloud Service and the Python Flask micro, web framework.</p><p>By taking the time to understand this code, one can really get a better appreciation of secure web technology, in general.</p><p>Thank you for reading!</p><h3>Final Code on GitHub:</h3><p><a href="https://github.com/ironspur5/python3-flask-oracle-idcs">ironspur5/python3-flask-oracle-idcs</a></p><h3>Resources:</h3><p><strong>Use Oracle Identity Cloud Service’s Software Development Kit (SDK) for Authentication in Python Web Applications: </strong><a href="https://www.oracle.com/webfolder/technetwork/tutorials/obe/cloud/idcs/idcs_python_sdk_obe/idcs-python-sdk.html">https://www.oracle.com/webfolder/technetwork/tutorials/obe/cloud/idcs/idcs_python_sdk_obe/idcs-python-sdk.html</a></p><p><strong>Authenticate a Python application with Oracle Identity Cloud Service: </strong><a href="https://docs.oracle.com/en/solutions/authenticate-sample-python-app-with-identity-cloud/learn-authentication-python-applications-and-oracle-identity-cloud-service1.html#GUID-2048B437-1DE8-445B-99D1-7053766044B1">https://docs.oracle.com/en/solutions/authenticate-sample-python-app-with-identity-cloud/learn-authentication-python-applications-and-oracle-identity-cloud-service1.html#GUID-2048B437-1DE8-445B-99D1-7053766044B1</a></p><p><strong>Review user and application access with Oracle IDCS Audit Reports: </strong><a href="https://blogs.oracle.com/imc/review-user-and-application-access-with-oracle-idcs-audit-reports">https://blogs.oracle.com/imc/review-user-and-application-access-with-oracle-idcs-audit-reports</a></p><p><strong>Flask vs Django: </strong><a href="http://www.mindfiresolutions.com/blog/2018/05/flask-vs-django/">http://www.mindfiresolutions.com/blog/2018/05/flask-vs-django/</a></p><p><strong>Authorization Code Grant Type: </strong><a href="https://docs.oracle.com/en/cloud/paas/identity-cloud/rest-api/AuthCodeGT.html">https://docs.oracle.com/en/cloud/paas/identity-cloud/rest-api/AuthCodeGT.html</a></p><p><strong>Generate Access Token and Other OAuth Runtime Tokens to Access the Resource: </strong><a href="https://docs.oracle.com/en/cloud/paas/identity-cloud/17.4.2/rest-api/op-oauth2-v1-token-post.html">https://docs.oracle.com/en/cloud/paas/identity-cloud/17.4.2/rest-api/op-oauth2-v1-token-post.html</a></p><p><strong>Sessions in Flask: </strong><a href="https://overiq.com/flask-101/sessions-in-flask/">https://overiq.com/flask-101/sessions-in-flask/</a></p><p><strong>Oracle Identity Cloud Service’ SDK Python Sample Application: </strong><a href="https://github.com/oracle/idm-samples/tree/master/idcs-sdk-sample-apps/python">https://github.com/oracle/idm-samples/tree/master/idcs-sdk-sample-apps/python</a></p><p><strong>Parameters as Query String Values: </strong><a href="https://howto.caspio.com/parameters/parameters-as-query-string-values/">https://howto.caspio.com/parameters/parameters-as-query-string-values/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c71cc6b3fa03" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>