Authenticating AngularJS against OAuth 2.0 / OpenID Connect

I’ve recently found myself doing quite a bit of work putting in place an STS (Security Token Service) based around the excellent¬†Thinktecture IdentityServer 3. I have a variety of different client types that need to authenticate including JavaScript Single Page Applications using the AngularJS framework.

IdentityServer 3 implements the Open ID Connect protocol for clients to authenticate against, Open ID Connect being an extension to OAuth 2.0.

There’s an existing open source plugin for authenticating with OAuth 2.0 called oauth-ng¬†that utilises the implicit authentication flow that I wanted to use however I wanted some different behaviour and was interested in implementing my own plugin as a learning exercise with the protocol itself and with AngularJS. Massive credit to the author of that plug-in for inspiration and readable code, this is the first non-trivial AngularJS directive I’ve developed and so it was incredibly useful to be able to look at oauth-ng and riff off it’s design. As another reference the Adal-Angular project was also really useful.

The main features of the plugin I’ve developed are:

  • Sign in / sign out button
  • Specify which routes require a token for access to protected resources and automatically handle sign in if required when they are accessed
  • Storage of the token in the browsers session storage via the ngStorage module
  • Automatic insertion of a bearer token into HTTP requests once a user has authenticated

The plug-in can be found on GitHub here. It works and I’ve tested it against both Thinktecture IdentityServer3 and Google’s OAuth2 endpoint but is still quite early code in terms of testing and so if you encounter any issues please do log them on the GitHub issues page or submit a pull request with a fix.

All the code samples given below are from the sample app which is basically the Yeoman generated scaffold and that you can find on GitHub here and which I’ve configured to authenticate directly against Google. You will need to obtain your own client ID and configure Google as per the instructions here.

Getting Started

You can either grab the scripts from GitHub or, more easily, install the plugin as a bower package which you can install as follows:

bower install angularjs-oauth2 --save

First you’ll need to add the module to your applications list of dependencies in app.js and you need to make sure that ngStorage is also included:

angular
  .module('angularJsApp', [
    'ngAnimate',
    'ngCookies',
    'ngResource',
    'ngRoute',
    'ngStorage',
    'ngSanitize',
    'ngTouch',
    'afOAuth2'
  ])

The default template that is supplied for the sign in / out button expects to be placed inside a bootstrap navbar.

Typically in the above example you would select a scope appropriate to the resource you wished to access (if that terminology is confusing then I have a series of blog posts on the way as an intro to Open ID Connect and OAuth 2.0) – I’ve just picked one that we’ll have access to without additional configuration.

Now let’s modify the app.js file so that clicking the About link will require the user to be signed in:

.config(function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controller: 'MainCtrl'
      })
      .when('/about', {
        templateUrl: 'views/about.html',
        controller: 'AboutCtrl',
        requireToken: true
      })
      .otherwise({
        redirectTo: '/'
      });
  });

Note the addition of requireToken:true to the route for about.

Now run the app (if you’re using the yeoman builder like myself then type grunt serve). You should see something much like the following appear in your browser:

indexScreen

The only difference from the standard Yeoman template (at least at the time I wrote this) is the Sign In button at the top right. If you click that, or the about link, then you should be redirected to the Google sign in page that looks like this:

signonSuccess

If you’ve not got things wired up “just so” in the Google console then you’ll see an error. Generally they are reasonably informative and normally, in my experience, the redirect URI in the console doesn’t quite match the redirect URI in the app (they’re sensitive over things like the / on the end or not).

After signing in the token will be added as an Authorization header to all http calls using the ‘Bearer my token’ format and so any calls you make to a remote resource that require authorisation will be supplied the token they need to verify the user.

Options

The oauth2 tag has a number of attributes that can be specified as follows.

authorisation-url: The URL to request the token from.
client-id: The client ID to supply to the token provider.
redirect-url: The URL the token provider should redirect to on a successful sign in.
response-type: Optional. The required token type. Defaults to token.
scope: The resources that access is requested to.
state: A magic number to send to the token provider to protect against CSRF attacks.
template: Optional. URL of a Angular template to use for the sign in / out button in place of the built in template.
buttonClass: Optional. Defaults to “btn btn-primary”. The class to apply to the button in the standard template.
signInText: Optional. Defaults to Sign In.
signOutText: Optional. Defaults to Sign Out.
signOutUrl: Optional. The URL to call to ask the token provider to perform a sign out. See notes on sign out below.
signOutAppendToken: Optional. Defaults to “false”. If set to “true” then the access token will be appended to the signOutUrl.
signOutRedirectUrl: Optional. The URL that the token provider, if it supports redirects, should redirect to following a sign out.

Signing Out

Signing out in the OAuth world can be… complicated. When the user presses the sign out button presented by this plug in the token that is stored in session storage is cleared and as we’re using session storage as soon as the session ends (window or tab closed) then they’ll be logged out.

However they may still be logged in with the token provider depending on how the token provider behaves and the options the user has selected their.

This plugin does allow a URL to be supplied to initiate a logout at the token provider where the token provider allows for that but if the token provider is using a persistent cookie and the user shuts the window without clicking sign out then they could remain logged in.

It’s worth thinking about if / when you choose to use OAuth.

15 thoughts on “Authenticating AngularJS against OAuth 2.0 / OpenID Connect

  1. Shannon Hill

    Hi James,

    Thanks a lot for putting this project together. It has helped me immensely! I am using your plugin with IdentityServer3 and I am having trouble getting the routing to work properly when the token request returns from the IdentityServer.

    For example, I am getting URLs of the form:
    http://localhost:9000/#/access_token=eyJ0eXAiOi1Q….

    Which I am finding difficult to properly route to display the necessary view. Other examples of similar plugins seem to include a “#/callback/access_token” prefix that makes it a snap to get the routing right. Am I missing something here or did you have this same issue? Thanks for any help you can provide.

    Thanks,

    -Shannon

    Reply
      1. Shannon Hill

        Thanks for the reply, James. I was able to work through this issue by using info from that workaround.

        Two other items questions I have:

        1. You mention ‘Alternatively protect your entire website’ as one of the features of the plugin. Curious as to how you’ve implemented that. I see it being pretty straightforward to protect individual WebAPI’s via HTTP intercepts, but what about the rest of an angular site? My current angular site is hosted in an ASP.Net MVC6 website, but there is no compiled code, only the angular stuff. I haven’t been looking forward to pulling down a token with MVC and then storing it somewhere on the page to then pass it off to your plug-in for further use… but it looks like that might be the only solution. Curious as to how you’ve tackled that issue.

        2. Lastly, this works great for intercepting calls to the $http service, but doesn’t have any effect on $resource calls, which, I believe, use $http behind the scenes. Have you run across this issue?

        Thanks for this great plugin and your guidance.

        -Shannon

        Reply
        1. James Post author

          Hi Shannon,

          Thanks for the hat tip on point (1) – you can only protect HTTP calls that require a token, not quite sure why I wrote that. Over eagerness!

          With regard to $resource calls – I’ve not come across any problems myself but I make much more use of $http. As $resource wraps $http it should work fine – all the interceptor does is add the Authorisation header to the request.

          James

          Reply
  2. Rob

    Hi James,

    I don’t suppose you could post a quick IdentityServerV3 example of your oauth2 tag could you? I’m rather new to both Angular and IdentityServer so any help would be appreciated. The bit that confuses me the most is the “client-id”, is this necessary for IdentityServer?

    Many thanks in advance,

    Rob

    Reply
    1. James Post author

      Hi Rob,

      Sorry it took me a while to reply but I wanted to put a proper sample together rather than something half baked and confusing. That’s now up in GitHub here. The index.html file contains the oauth2 tag in use. Hope that’s helpful.

      With regard to the client-id – when you create a client in IdentityServer you give it an ID and it’s this ID you need to use here. You can see that defined in Clients.cs in the IdentityServerSample.STS project. You can see that the ID matches the one in the oauth2 tag.

      This is necessary because your identity server could contain support for multiple clients with different authentication flows and configuration and you need to point your consuming app at the correct one.

      Hope that helps.

      James

      Reply
  3. Peter Steele

    James,

    I have the component working with identityServer3, and I really like what you’ve done. But what I want to do is extract user claims from the identity token after successful login.

    How do I respond to an event on successful logon, and then parse information from the identity token such as in
    https://github.com/IdentityServer/IdentityServer3.Samples/tree/master/source/Clients/JavaScriptImplicitClient

    I built and ran your IdentityServer sample, and that works good, but I would like to trigger the app to do things like update the current username after login, and I can’t figure out how to do that, since the app completely re-loads, how can I check to see if someone is logged in and get the IdentityToken?

    Thanks!

    -Peter

    I have tried hooking to

    Reply
    1. James Post author

      Hi Peter,

      You can grab the token itself from session storage – it’s stored against a key of (funnily enough) ‘token’ and you can extract the claims by decoding it yourself or by using something like auth0/jwt-decode. They’re basically just base 64 encoded.

      To take an action when the app is reloaded following the redirect chain you can subscribe to the event oauth2:authSuccess.

      Hope that helps and sorry for the delay in replying. Busy month!

      James

      Reply
  4. Mohan Radhakrishnan

    How do you provision IdentityServer 3 on Azure ? I have code in my VSO’s Git that will access IdentityServer 3. Is there any documentation on this ?

    Reply
  5. Sandagomi

    HI James,

    Finally i found a useful source to ask my question/ i am using angularOAuth2.js in my project. and i am using the directive as follow

    my solution works fine.
    but according to this when i got a session timeout this will refresh the token and redirect to the “https://localhost:44322/” but how can we redirect it to “login page” or rather to “https://localhost:44320/connect/authorize” url… please give me some instructions

    Reply
    1. James Post author

      Hi, I can’t see your Angular directive example but if you’re routing includes the requireToken: true option then on encountering an expired token the plugin should redirect you to the authorization endpoint automatically.

      Another option, to extend the session, is to use silent token renewal which was added recently and is documented here https://github.com/JamesRandall/AngularJS-OAuth2. Essentially a couple of minutes before the session is due to expire this will use a hidden iframe to contact the authorization endpoint and obtain a new token.

      Hope that helps.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *