<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Benjamin Wanicur</title>
    <description>The latest articles on Forem by Benjamin Wanicur (@bwanicur).</description>
    <link>https://forem.com/bwanicur</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F138700%2F0919bf4c-fae5-416b-98b3-48eba798dc2a.jpeg</url>
      <title>Forem: Benjamin Wanicur</title>
      <link>https://forem.com/bwanicur</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bwanicur"/>
    <language>en</language>
    <item>
      <title>Finding Effective Consultants</title>
      <dc:creator>Benjamin Wanicur</dc:creator>
      <pubDate>Sat, 20 Apr 2019 18:12:37 +0000</pubDate>
      <link>https://forem.com/bwanicur/finding-effective-consultants-3a4j</link>
      <guid>https://forem.com/bwanicur/finding-effective-consultants-3a4j</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;a href="https://middlepathdevelopment.com/blog/finding-effective-consultants.html"&gt;Original Post Here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Finding consultants can be a real challenge in today's market.  This article is aimed at non-technical business owners or whoever might be in charge of hiring technical talent.  When I say "consultant", I am primarily talking about &lt;em&gt;programmers&lt;/em&gt;, but as we will see, it takes more than being a technically skilled programmer to be an effective consultant.&lt;/p&gt;




&lt;h3&gt;
  
  
  Holes In My House's Foundation
&lt;/h3&gt;

&lt;p&gt;Sorry to bring out the tired house metaphor, but I still believe it is very appropriate for this kind of discussion.  Every consultant I've met has their horror stories about inheriting an application with some serious technical problems.  A side note to consultants here:  Yes we have all been there, but "hindsight is 20/20" (or insert other cliche here).  We do not know what was happening at the time, so try to reserve judgement about the previous programmers.  But by all means, please be critical of the code!&lt;/p&gt;

&lt;p&gt;Here is an example (one I've encountered a few times).  The previous programmer(s) built a database that has problems in the way the data is organized.  Then they wrote application code that runs on that database and its "less than desirable" organizational scheme.  This is a really terrible situation because it costs exponentially more the longer it goes unaddressed. In this example not only do we need to fix lots of code, but customer data needs to be migrated (to the better organized database scheme). Customer data migration is a tedious and scary process.&lt;/p&gt;

&lt;p&gt;Hopefully you do not find yourself in this situation.  If you have a trusted friend with some techincal experience, you may be able to lean on them to help you find a technically competent consultant.  I also believe this scenario is less common that other challenging situations (hint: read on) you might find working with consultants.&lt;/p&gt;




&lt;h3&gt;
  
  
  Rockstars, Gurus, and things that go bump in the night
&lt;/h3&gt;

&lt;p&gt;So you have found your technical wizard and everything will be smooth sailing from here on, right ?  If you've been following along you probably know I am going burst your bubble.&lt;/p&gt;

&lt;p&gt;First let's take a minute to address some buzz words you should probably avoid when searching for consultants.  &lt;em&gt;Rockstar&lt;/em&gt;, &lt;em&gt;guru&lt;/em&gt;, &lt;em&gt;wizard&lt;/em&gt; are a few that come to mind.  Looking at technical forums or email lists that allow recruitment posts, you will find that in the past few years these buzz words will ruffle many programmers' feathers and before you know it, the pitchforks and torches have come out.  So why is that ?&lt;/p&gt;

&lt;p&gt;In software development, it "takes a village".  The tired cliche of a nerd, locked in room with red bull and year's supply of gummy bears has been played out and only belongs in TV or movies.  Collaboration is king and no one knows everything.  You might be thinking: &lt;em&gt;I cannot afford to hire a team of programmers.  I need one person who can do the job&lt;/em&gt;.  I am not being literal with the term "village", so let's talk about the single consultant scenario.&lt;/p&gt;

&lt;p&gt;Having your one "rockstar" consultant might work if the following are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You know exactly what features you need for your web application ahead of time&lt;/li&gt;
&lt;li&gt;You are aware of the trade offs that different technologies or solutions present&lt;/li&gt;
&lt;li&gt;You know that none of your requirements will ever change over the course of the project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this is the case, go ahead and buy the red bull and gummy bears, lock your consultant in a room, and get ready for profit!&lt;/p&gt;

&lt;p&gt;Ok... I've &lt;strong&gt;never&lt;/strong&gt; worked on a project like that.  Requirements always change.  Most business owners are not aware of every technical trade off and rarely know how much work goes into each piece of their web application.  If they did, they are probably a programmer and would do the work themselves.&lt;/p&gt;

&lt;p&gt;Being a rockstar or guru developer alone is not enough be effective.  I have talked enough about the challenges.  Next, let's talk about how to find and work with effective consultants.&lt;/p&gt;




&lt;h3&gt;
  
  
  Effective Consultants
&lt;/h3&gt;

&lt;p&gt;Being technically proficient should be the &lt;em&gt;starting point&lt;/em&gt; for consultants.  But it is just as important to become a trusted advisor and direct projects along paths that will lead to less heartache down the road.&lt;/p&gt;

&lt;p&gt;It is vital to get consultants involved early in the process.  I understand there is an impulse to save billable hours by handling the planning without a consultant's help.  However, mis-steps at this stage can result in many hours of back tracking that might have been avoided.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are no solutions, only trade offs. - &lt;cite&gt;Thomas Sowell&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I know that sounds daunting, but it is very true, and I think it embodies the kind of advice you should be getting from your consultant.&lt;/p&gt;

&lt;h4&gt;
  
  
  Here is an example:
&lt;/h4&gt;

&lt;p&gt;Client will ask what seems like a very simple, straight-forward question, like this: &lt;em&gt;How much work would it take to make an autocomplete search bar on this page ?&lt;/em&gt;  My answer starts a conversation that is probably more involved that my client thought.  We have to talk about the pros and cons of each possible solution.  We need to consider the end goal of the application.  Is this a proof of concept application ?  Is it something that needs to be ready for real-world consumer use when we are done ?  How important is this feature compared to other features we want and how does that fit in with the budget ?  Although this conversation might not have been the answer my client expected, I believe I am doing my job when we have these conversations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cry in the dojo, laugh on the battlefield.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Putting in the extra time by discussing the pros and cons of each decision can save hours and heartache later.  I am very skeptical of consultants who are very &lt;em&gt;tribal&lt;/em&gt; about their technologies.  If consultant answers the question above (the autocomplete search example) with something like this: &lt;em&gt;We should just use X because it is the best&lt;/em&gt;, please be wary.&lt;/p&gt;

&lt;p&gt;Also beware the consultant who wants to build technology for technology's sake.  They might want to try out this new framework because it is cool.  Or over-engineer some solution based on an ego trip or some misconception about what a client needs to meet their goals.  I think these scenarios are not very common for consultants who have been around the block more than once, but it is worth mentioning.&lt;/p&gt;




&lt;h3&gt;
  
  
  Finding Your Effective Consultants
&lt;/h3&gt;

&lt;p&gt;Now that you know what to look for, how do you find these magical consultants ?  I wish there were a tried and true method, but there is not.  Even if you know from experience (perhaps a personal recommendation from a trusted friend) that a consultant is effective, we are all people with varying personalities and sometimes we clash.&lt;/p&gt;

&lt;p&gt;Because of this uncertainty I am a big proponent of the &lt;strong&gt;Test Drive&lt;/strong&gt;.  Every large project can hopefully be broken up into smaller chunks.  By "small chunk" I mean something that can be accomplished in few days of work or less.  Once you have focused in on a potential consultant, see if you can find one of these smaller chunks and hire the consultant to complete the work.  If the consultant is good and you have chosen something that is &lt;em&gt;not&lt;/em&gt; a small chunk, they should be able to tell you that right away.  Once the work is done, you should know if you like working with the consultant and vice-versa.  If not, you should still have a chunk of completed work and you've only spent small amount of money.&lt;/p&gt;

&lt;p&gt;I hope my opinions and thoughts about finding consultants has been helpful.  If you have questions or want to chat, you can always reach me here.&lt;/p&gt;

</description>
      <category>career</category>
      <category>management</category>
    </item>
    <item>
      <title>OAuth2 Authorization Code Flow for Single Page Apps   </title>
      <dc:creator>Benjamin Wanicur</dc:creator>
      <pubDate>Mon, 15 Apr 2019 16:55:09 +0000</pubDate>
      <link>https://forem.com/bwanicur/oauth2-authorization-grant-flow-for-single-page-apps-53oc</link>
      <guid>https://forem.com/bwanicur/oauth2-authorization-grant-flow-for-single-page-apps-53oc</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;a href="https://middlepathdevelopment.com/blog/authorization-code-flow-for-single-page-apps.html" rel="noopener noreferrer"&gt;Original Post&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can implement the traditional OAuth &lt;strong&gt;Authorization Code Flow&lt;/strong&gt; if you have access to both your SPA and backend server.  This may be useful for supporting older OAuth providers.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Recently I was tasked with integrating several 3rd party services into an application.   Most of these services use the OAuth2 protocol to handle authentication and authorization.  I have worked with OAuth many times, but this was my first time integrating OAuth authenticated services into a Single Page Application (SPA) / backend micro services (API and more) architecture.  Having to work with a backend API and SPA introduced some interesting wrinkles.&lt;/p&gt;

&lt;p&gt;Single page apps might use the &lt;em&gt;Implicit Flow&lt;/em&gt; for OAuth.  The Implicit Flow is usually for client-side / SPA type applications when there is no access to the backend server.  Maybe the app uses something like Firebase as a backend.  The solution outlined in this article is using the &lt;em&gt;Authorization Grant Flow&lt;/em&gt;.  This approach will not be easy unless one has access / control of the backend server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A FEW GROUND RULES:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This solution involves some redirecting and hence reloading of the SPA.  If this is problematic or unacceptable for your needs, feel free to jump ship right now.&lt;/li&gt;
&lt;li&gt;The registered redirect URI might not have the same domain as the SPA.&lt;/li&gt;
&lt;li&gt;This solution assumes we are using JWT or some kind of token-based authentication for our own SPA application.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implicit or Authorization Code Flow ?
&lt;/h3&gt;

&lt;p&gt;This article is not attempting to address the question of which OAuth2 flow is best for your application.  For SPAs it seems the industry has moved away from the Implicit Flow and over to using Authorization Code flow, with a &lt;strong&gt;public client&lt;/strong&gt;.  This flow is slightly different.  No secret authorization code is exchanged (this is in Step 2 - see below).  Since I had access and control over the backend services, I choose to go with the traditional Authorization Code flow.   &lt;/p&gt;

&lt;p&gt;Does this all sound like Greek to you ?  If so, do not worry.  We are not getting into the weeds of OAuth2 nor will we disappear down the rabbit hole of arguing about which flow is “the one right way”.  The best solution depends on the goals of your application.  This article is going to describe one solution.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Brief OAuth2 Overview
&lt;/h3&gt;

&lt;p&gt;Even if everything written above is completely new to you, chances are you have used OAuth2 enabled applications.  Especially if you’ve used any kind of Single Sign On buttons.  Think about those “Sign in with (Google|Facebook|Github|etc…)” buttons you see all over the place.&lt;/p&gt;

&lt;p&gt;When you go to authorize one of these applications, the first thing that happens is that you must authenticate yourself via username and password.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhgaxbho5fw827t8ut2k3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhgaxbho5fw827t8ut2k3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you have proven that you are yourself, you must give the OAuth2 application permission to do various things on your behalf.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F7z9qmkdeyqz715760ktx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F7z9qmkdeyqz715760ktx.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have gone through the “OAuth2 song and dance”, the response from the OAuth2 provider should include an access token that we can use to access the 3rd party application.  Maybe that includes giving our application access to a user’s Google contacts or emails.  Or giving our application access to a user’s Facebook group.&lt;/p&gt;

&lt;p&gt;One important OAuth2 detail.  This redirect URL is something that needs to be registered ahead of time with the OAuth2 provider.  &lt;/p&gt;

&lt;p&gt;Here is an example from a Google OAuth2 config page, where localhost is used in the redirect URI.  Note the "Authorized Redirect URIs section".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6pzuxhs3xwk6arpwk913.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6pzuxhs3xwk6arpwk913.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE&lt;/em&gt;: Some OAuth2 providers will not allow "localhost" as a redirect domain.  SSH tunneling / port forwarding can be used for development purposes to get around that issue.&lt;/p&gt;

&lt;p&gt;The entire simplified flow looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdocs.google.com%2Fdrawings%2Fd%2Fe%2F2PACX-1vQnYFLXp2VUBCUxbj5SL9_tXYhrqg_1Q1w7nc-dO6zXyvcJ7UdE0izWjMR2qzdFoGYIOaQThIVXrf9d%2Fpub%3Fw%3D1440%26h%3D1080" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdocs.google.com%2Fdrawings%2Fd%2Fe%2F2PACX-1vQnYFLXp2VUBCUxbj5SL9_tXYhrqg_1Q1w7nc-dO6zXyvcJ7UdE0izWjMR2qzdFoGYIOaQThIVXrf9d%2Fpub%3Fw%3D1440%26h%3D1080"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You could imagine that this takes place somewhere on a configuration or settings page.  When the user decides to "enable" a 3rd party service, behind the scenes the OAuth song and dance is happening.  In the end we have an access token.  And sometimes more...&lt;/p&gt;

&lt;h3&gt;
  
  
  Open ID Connect (OIDC)
&lt;/h3&gt;

&lt;p&gt;Oh no!  Another acronym.   In official terminology OIDC is a “Profile” of OAuth2.  That just means, it is like a “flavor” of OAuth2.  What is important about OIDC is that we not only get back an access token, but we also get back an “ID Token”.  This is a unique identifier that can be linked to our user.&lt;/p&gt;

&lt;p&gt;When we get the response from our OIDC-enabled provider, we will store the id token and associate it with our user.  In our workflow (see the next image), the user is already “logged in”, thus we can associate the ID Token with our user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Single Sign On
&lt;/h3&gt;

&lt;p&gt;The simple OAuth2 flow (described above) has already been handled.   Therefore, we should already have stored the ID Token that was returned from Step 2.  Now we have all the pieces in place, here is the big plan:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdocs.google.com%2Fdrawings%2Fd%2Fe%2F2PACX-1vS9rFUUkIViEfnAzQEXFrdughwuMxz8dScTodDwNeOrYCKhJld9EUlx44EYMPthEFjJSlcmaaFUzsKl%2Fpub%3Fw%3D1440%26h%3D1080" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdocs.google.com%2Fdrawings%2Fd%2Fe%2F2PACX-1vS9rFUUkIViEfnAzQEXFrdughwuMxz8dScTodDwNeOrYCKhJld9EUlx44EYMPthEFjJSlcmaaFUzsKl%2Fpub%3Fw%3D1440%26h%3D1080"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s start from the beginning.  The user clicks on our SSO button (“Login with Google”).  If the user is already logged into Google, Steps 1 and 2 will happen transparently.  If not, the user will have to authenticate with username / password. &lt;/p&gt;

&lt;p&gt;The ID Token from Step 2 can be used to identify our user!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT * FROM users WHERE &amp;lt;xxxx&amp;gt;.id_token = &amp;lt;ID Token&amp;gt;&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;This is pseudo-SQL.  This article is not addressing how or where to store tokens&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We now have our user!  Our SPA is stateless and uses its own set of JWT tokens to authenticate requests.  That means, for our user to be “logged in”, their requests need to be accompanied by a valid JWT token each time. &lt;/p&gt;

&lt;p&gt;At this point, many cookie/session based solutions could be implemented, instead of this article’s solution.  We could create and stuff the JWT token into the cookie and then the user will be “logged in”.  However, there are some limitations with the cookie-based solution.  For one, it only works easily if the SPA and Backend Server are on the same domain (frontend.myapp.com and api.myapp.com).  There are ways around that challenge, however, our solution bypasses the need for cookies.  We can also avoid other security concerns for cookies with this approach.  Ok, we are not using cookies.  What’s next ?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt;  We have identified the user.  Why not just create the JWT and include it with the redirect back to our SPA ?  &lt;/p&gt;

&lt;p&gt;It turns out there is a major problem with that approach.  The JWT token can be stolen and reused by nefarious users to impersonate your user.  Therefore, redirecting our initial GET request back to our SPA (Step 3) with the JWT token included in the URL is unsafe.  That URL will be in the browser history among other places.&lt;/p&gt;

&lt;p&gt;Since we do not want to redirect back to the SPA with anything sensitive in the URL, we can create a short-lived, one-time-use token.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt;  The SPA will see this token in the URL.  The SPA will immediately make a POST request to the Backend Server with the SSO token. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt;  The Backend Server will use the SSO token to identify the user, immediately invalidate the SSO token, and then respond with the JWT token securely in the POST body.  User is “logged in.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Good / Bad from this solution
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redirects ?  In many SPA situations the backend server has one job: output JSON.  Since we are redirecting here, we break that simple rule.  Now our backend server only outputs JSON…. except for this &lt;em&gt;one time&lt;/em&gt;.   In our case, I think this sacrifice was worth the gain.&lt;/li&gt;
&lt;li&gt;An extra token to manage.  It &lt;em&gt;should&lt;/em&gt; only exist briefly, however, it is another moving piece that can break. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Good:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No cookies !
&lt;/li&gt;
&lt;li&gt;Stateless which is consistent with how many SPAs operate.&lt;/li&gt;
&lt;li&gt;Using Authorization Code flow assures that older OAuth providers (who might not use encrypted data transfers) may only be accessible through this flow.  Implicit flow (and OAuth2 in general) requires encrypted data transfer.  &lt;strong&gt;This was the winning point in choosing this approach&lt;/strong&gt;.  It turns out that the project needed to support some smaller OAuth providers who were outdated and did not always adhere to OAuth's security standards.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Links:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://oauth.net/2/" rel="noopener noreferrer"&gt;OAuth 2.0 — OAuth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aaronparecki.com/oauth-2-simplified/" rel="noopener noreferrer"&gt;OAuth 2 Simplified • Aaron Parecki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;This is outlines a similar approach but with a PUBLIC client.   This can be implemented when there is no access to a backend server.  Also great security notes.  &lt;a href="https://medium.com/@robert.broeckelmann/securely-using-the-oidc-authorization-code-flow-and-a-public-client-with-single-page-applications-55e0a648ab3a" rel="noopener noreferrer"&gt;SECURELY USING THE OIDC AUTHORIZATION CODE FLOW AND A PUBLIC CLIENT WITH SINGLE PAGE APPLICATIONS&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>oauth</category>
      <category>singlepageapp</category>
    </item>
  </channel>
</rss>
