OWIN Testing with Identity Server

OWIN Testing with Identity Server

12 September 2015

Thinktecture IdentityServer V3 is now core to the platform that I contribute to in my company. Thing is I feel that I integrated it with speed while forfeiting a solid understanding I should have gained. I want to know what the different flows are, and how the browser and server plays a role.

I have created some tests to get me started and to help me to start to understand the detail. They are available on Bitbucket.

I am going to elaborate on some flows and use Api Blueprint to describe the requests and responses.

Let's Rock!

Client Credentials Grant

This is one of the simple flows.
A client will exchange credentials for an access_token.

Requesting a token /connect/token

I am posting here but I believe this can also be a get.

# POST https://idp/connect/token

+ Request (application/x-www-form-urlencoded)

    + Body

        client_id=chw&client_secret=secret&grant_type=client_credentials&scope=profile special_access

+ Response 200 (application/json)

    + Body

        {
          "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJjbGllbnRfaWQiOiJjaHciLCJjbGllbnRfY2h3OmRlbGV0ZSI6ImFjY2VzcyIsInNjb3BlIjpbInByb2ZpbGUiLCJzcGVjaWFsX2FjY2VzcyJdLCJpc3MiOiJodHRwczovL2lkc3J2My5jb20iLCJhdWQiOiJodHRwczovL2lkc3J2My5jb20vcmVzb3VyY2VzIiwiZXhwIjoxNDQxOTc3MDMzLCJuYmYiOjE0NDE5NzY2NzN9.JEJGv6AHJg4J0PJDw66RdioLx4OzYwS9v01ZY26zkcZnMDGsIycBJ9pXA2wIEU8mgktJHvMRtMEJ9KD79Bk8EC5waAmaI7JhXpcLP8vYSLi5TdxP8lXd8l8_0HnGYzkv76z1b8QaGq4s-EZ__VxIl8DejriSuo5yQ4RAGIbrZqcHfeUNdS5KkYYVpPz3BDCYKblMsCmCDt7HNcD-LSs_kX6mwlms5R6u_R6hpqgLmqZvOm7SFU-Lda6JypnI_XOXMPeltNamyuOja1lvgydmBoR2kyjJw9mPO6ollNlR3KOoxIcN1seo-f42lKJsQZ951c0mSiJ-xC1Rw2YgBVnDeg",
          "expires_in": 360,
          "token_type": "Bearer"
        }
access_token JWT payload

Below is the jwt payload from the access_token.

{
  "client_id": "chw",
  "client_chw:delete": "access",
  "scope": [
    "profile",
    "special_access"
  ],
  "iss": "https://idsrv3.com",
  "aud": "https://idsrv3.com/resources",
  "exp": 1441977033,
  "nbf": 1441976673
}

Resource Owner flow

The Resource Owner Password Credentials allows a client to pass user credentials to the Idp.

Requesting a token /connect/token

# POST https://idp/connect/token

+ Request (application/x-www-form-urlencoded)

    + Body

        client_id=chw_resource&client_secret=secret&grant_type=password&scope=profile special_access&username=colin&password=password

+ Response 200 (application/json)

    + Body

        {
            "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJjbGllbnRfaWQiOiJjaHdfcmVzb3VyY2UiLCJzY29wZSI6WyJwcm9maWxlIiwic3BlY2lhbF9hY2Nlc3MiXSwic3ViIjoiMTIzNDUiLCJhbXIiOiJwYXNzd29yZCIsImF1dGhfdGltZSI6MTQ0MjQxNTgzNywiaWRwIjoiaWRzcnYiLCJzcGVjaWFsMTAxIjoiYWRtaW4iLCJpc3MiOiJodHRwczovL2lkc3J2My5jb20iLCJhdWQiOiJodHRwczovL2lkc3J2My5jb20vcmVzb3VyY2VzIiwiZXhwIjoxNDQyNDE2MTk3LCJuYmYiOjE0NDI0MTU4Mzd9.RO6Svol8TzdZ3I4bynp0FuvIGNDObTTT8HRxBCLaqFjSXT0JILgqReT0SQ9cYGi7saaxSPisfBZ7EABdrUGgWq_UaN7gY5WbvJ9qopYBoa4Q4wKpw3UIQNPcXHIvbh4wrGS17dLXm7GKuTO1g-qp5Ey3P62lf8ITu-zabglLn-qvePzh6_TnN69N0DnFAlkd7B9hgKdZbOtEvvcJ4D1rwSCyk61XviTmVrETOn_eh9bCnbfNwp8TxqADZRsNwieRlzKvyP7i9kOf1wkffyKfvxnbqkxSUjUtE-W2cOFc7couvvdSxUwMK3iUHHcPRlqn43dnT5IyqOietL-oro6u5Q",
            "expires_in": 360,
            "token_type": "Bearer"
        }

The access_token in this case differs from client_credentials since it contains a subject (sub) claim.

access_token JWT payload

{
  "client_id": "chw_resource",
  "scope": [
    "profile",
    "special_access"
  ],
  "sub": "12345",
  "amr": "password",
  "auth_time": 1442415837,
  "idp": "idsrv",
  "special101": "admin",
  "iss": "https://idsrv3.com",
  "aud": "https://idsrv3.com/resources",
  "exp": 1442416197,
  "nbf": 1442415837
}

Implicit Grant

I tend to use this flow in single page applications (SPA) such as AngularJS driven pages.
When accessing a protected resource the application (Relying Party) will redirect to the IdentityProvider (IdP).

Visit https://localhost/Foo

As a consumer, using your web browser, you navigate to a wonderful application called Foo.
The browser is redirected to the Idp login page where you must enter your credentials.

# GET https://localhost/Foo

+ Response 302

    + Headers

        Location:  https://idp/connect/authorize?client_id=chw_implicit&response_type=id_token token&scope=openid profile special_access&state=194536517cc14866b3df079cc5cd7ee5&redirect_uri=https%3a%2f%2flocalhost%2fFoo&nonce=ea98658139bb46b1969fb82a93ac0e04
        

Redirect to https://idp/connect/authorize

The browser starts the authentication cycle and visits the /authorize endpoint.
The browser is redirected to an IdentityServer relative Url /signin uri.

# GET  https://idp/connect/authorize?client_id=chw_implicit&response_type=id_token%20token&scope=openid profile special_access&state=194536517cc14866b3df079cc5cd7ee5&redirect_uri=https%3a%2f%2flocalhost%2fFoo&nonce=ea98658139bb46b1969fb82a93ac0e04

+ Response 302

    + Headers

        Location:  https://idp/login?signin=a74f4850f7088cc10f9dad4a50b2983f

        Set-Cookie: SignInMessage.a74f4850f7088cc10f9dad4a50b2983f=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAKna-MzOwkEGxEnjNZzRaLQAAAAACAAAAAAADZgAAwAAAABAAAABPv_BZrOZXWdIdR3dU8OYPAAAAAASAAACgAAAAEAAAAEMHMlQ9G4KxV8atKHfjhD5QAQAA7KDCgmlpJoBUJJg3aelxaoWmOo_EVO4eLOMHpHsyuJbWUd4wO8uScJD28BniZCb5RjFR7yFhAeOKkfVXktjMY0gpaoMwir9vGIVaKJoqGc7j378QYDuO769AX6sN3YmX8h0vZglFWXaxv6C1I6wSuio2T3GF4UqDBNjGM3WRaMJ04Adf41-mQZMpvc7RjIiMPMjVyDr0RrnFaggaRoZgl5SDQRMwa9g-ppm5MJLf9AcTYBuuIAJSj7Y0JJOG9j8xXn2M_kf0zMU2OlX-lZrwtb-A6-zPQHLPB02W1v5mfX-vKhf2R2obVxVnM6ip6avNdTshMDZeLI1mckfCZl5BtrKm71DXKtas5UuxXZIfNTZb9w8wxeO2WnVane9S8bkmmSpIXi8ifiXdYsRH_8tIrlpVJZ2Lr-5aM_p5hR7fWwKwykHQq9F2YE3fR5Ng7MwcFAAAAB4sghZgVDXD3uanudTA6SCl6FKn

At this point the browser needs to persist a cookie SignInMessage.xxxx

Redirect to https://idp/login?signin for AuthN

Redirects to https://idp/login?signin where an Html page is returned presenting a login screen.

# GET https://idp/login?signin=a74f4850f7088cc10f9dad4a50b2983f

+ Request

    + Headers

        Cookie: SignInMessage.a74f4850f7088cc10f9dad4a50b2983f=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAKna-MzOwkEGxEnjNZzRaLQAAAAACAAAAAAADZgAAwAAAABAAAABPv_BZrOZXWdIdR3dU8OYPAAAAAASAAACgAAAAEAAAAEMHMlQ9G4KxV8atKHfjhD5QAQAA7KDCgmlpJoBUJJg3aelxaoWmOo_EVO4eLOMHpHsyuJbWUd4wO8uScJD28BniZCb5RjFR7yFhAeOKkfVXktjMY0gpaoMwir9vGIVaKJoqGc7j378QYDuO769AX6sN3YmX8h0vZglFWXaxv6C1I6wSuio2T3GF4UqDBNjGM3WRaMJ04Adf41-mQZMpvc7RjIiMPMjVyDr0RrnFaggaRoZgl5SDQRMwa9g-ppm5MJLf9AcTYBuuIAJSj7Y0JJOG9j8xXn2M_kf0zMU2OlX-lZrwtb-A6-zPQHLPB02W1v5mfX-vKhf2R2obVxVnM6ip6avNdTshMDZeLI1mckfCZl5BtrKm71DXKtas5UuxXZIfNTZb9w8wxeO2WnVane9S8bkmmSpIXi8ifiXdYsRH_8tIrlpVJZ2Lr-5aM_p5hR7fWwKwykHQq9F2YE3fR5Ng7MwcFAAAAB4sghZgVDXD3uanudTA6SCl6FKn

+ Response 200 (text/html)

    + Body

        <!DOCTYPE html>
        <html ng-app="app" ng-controller="LayoutCtrl">
        <head>
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <title>Thinktecture IdentityServer v3 - preview 1 (SelfHost)</title>
            <link href='/assets/styles.min.css' rel='stylesheet'>

        </head>
        <body lang="en">
            <div class="navbar navbar-inverse navbar-fixed-top">
                <div class="navbar-header">
                    <a href="">
                        <span class="navbar-brand">Thinktecture IdentityServer v3 - preview 1 (SelfHost)</span>
                    </a>
                </div>
                <ul class="nav navbar-nav" ng-show="model.currentUser" ng-cloak>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">{{model.currentUser}} <b class="caret"></b></a>
                        <ul class="dropdown-menu">
                            <li><a href="{{model.logoutUrl}}">Logout</a></li>
                            <li class="divider" ng-show="model.loginWithDifferentAccountUrl"></li>
                            <li><a href="{{model.loginWithDifferentAccountUrl}}" ng-show="model.loginWithDifferentAccountUrl">Login With Different Account</a></li>
                        </ul>
                    </li>
                </ul>
            </div>

            <div class='container page-login' ng-cloak>
                <div class="page-header">
            <h1>Login</h1>
        </div>        
        <div class="row" ng-show="model.errorMessage">
            <div class="col-md-12 col-sm-12">
                <div ng-show="model.errorMessage" class="alert alert-danger">
                    <strong>Error:</strong>
                    {{model.errorMessage}}
                </div>
            </div>
        </div>        
        <div class="row">
            <div class="col-md-6 col-sm-6" ng-show="model.loginUrl">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h3 class="panel-title">Local Login</h3>
                    </div>
                    <div class="panel-body">
                        <form name="form" method="post" action="{{model.loginUrl}}">
                            <anti-forgery-token token="model.antiForgery"></anti-forgery-token>

                            <fieldset>
                                <div class="form-group">
                                    <label for="username">Username</label>
                                    <input required name="username" autofocus id="username" type="text" class="form-control" placeholder="Username" ng-model="model.username" maxlength="100">
                                </div>
                                <div class="form-group">
                                    <label for="password">Password</label>
                                    <input required id="password" name="password" type="password" class="form-control" placeholder="Password" ng-model="model.password" maxlength="100" autocomplete="off">
                                </div>
                                <div class="form-group login-remember" ng-show="model.allowRememberMe">
                                    <label for="rememberMe">
                                        <input type="checkbox" id="rememberMe" name="rememberMe" ng-model="model.rememberMe" value="true">
                                        <strong>Remember My Login</strong>
                                    </label>
                                </div>
                                <div class="form-group">
                                    <button class="btn btn-primary">Login</button>
                                </div>
                            </fieldset>
                        </form>
                    </div>
                    <ul class="list-unstyled">
                        <li ng-repeat="link in model.additionalLinks"><a ng-href="{{link.href}}">{{link.text}}</a></li>
                    </ul>
                </div>
            </div>        
            <div class="col-md-6 col-sm-6 external-providers" ng-show="model.externalProviders">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h3 class="panel-title">External Login</h3>
                    </div>
                    <div class="panel-body">
                        <ul class="list-inline">
                            <li ng-repeat="provider in model.externalProviders">
                                <a class="btn btn-default" href="{{provider.href}}">{{provider.text}}</a>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>        
            </div>        
            <script id='modelJson' type='application/json'>{&quot;loginUrl&quot;:&quot;/login?signin=a74f4850f7088cc10f9dad4a50b2983f&quot;,&quot;antiForgery&quot;:{&quot;name&quot;:&quot;idsrv.xsrf&quot;,&quot;value&quot;:&quot;AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAKna-MzOwkEGxEnjNZzRaLQAAAAACAAAAAAADZgAAwAAAABAAAAB1X6PxhHhUL5qahWZy4G86AAAAAASAAACgAAAAEAAAAJnpFCk9aNKcfwg4WIHgeyQYAAAAMLvP4ORXNAJmIuHyGwJzk3D7QhIVVmqKFAAAALUCy2tKYHbZZ7J4FSPuzWYfftlK&quot;},&quot;allowRememberMe&quot;:true,&quot;rememberMe&quot;:false,&quot;username&quot;:null,&quot;externalProviders&quot;:[],&quot;additionalLinks&quot;:null,&quot;errorMessage&quot;:null,&quot;requestId&quot;:&quot;945025e0-bab8-4af6-826a-f6808b457c16&quot;,&quot;siteUrl&quot;:&quot;https://idp/&quot;,&quot;siteName&quot;:&quot;Thinktecture IdentityServer v3 - preview 1 (SelfHost)&quot;,&quot;currentUser&quot;:null,&quot;logoutUrl&quot;:&quot;https://idp/logout&quot;}</script>
            <script src="/assets/scripts.js"></script>            
        </body>
        </html>       

An antiforgery token is available in //script[@id='modelJson'] which binds to a form field called idsrv.xsrf.

Username and password is posted for AuthN

The user enters their username and password then clicks the 'Login' button which submits the form. To pass the antiforgery check it seems I had to supply a cookie called idsrv.xsrf which seems to have a different value to the form value. Adding 'Understand antiforgery' to my things to understand list!

# POST https://idp/login?signin=a74f4850f7088cc10f9dad4a50b2983f

+ Request (application/x-www-form-urlencoded)

    + Headers

        Cookie: SignInMessage.a74f4850f7088cc10f9dad4a50b2983f=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAKna-MzOwkEGxEnjNZzRaLQAAAAACAAAAAAADZgAAwAAAABAAAABPv_BZrOZXWdIdR3dU8OYPAAAAAASAAACgAAAAEAAAAEMHMlQ9G4KxV8atKHfjhD5QAQAA7KDCgmlpJoBUJJg3aelxaoWmOo_EVO4eLOMHpHsyuJbWUd4wO8uScJD28BniZCb5RjFR7yFhAeOKkfVXktjMY0gpaoMwir9vGIVaKJoqGc7j378QYDuO769AX6sN3YmX8h0vZglFWXaxv6C1I6wSuio2T3GF4UqDBNjGM3WRaMJ04Adf41-mQZMpvc7RjIiMPMjVyDr0RrnFaggaRoZgl5SDQRMwa9g-ppm5MJLf9AcTYBuuIAJSj7Y0JJOG9j8xXn2M_kf0zMU2OlX-lZrwtb-A6-zPQHLPB02W1v5mfX-vKhf2R2obVxVnM6ip6avNdTshMDZeLI1mckfCZl5BtrKm71DXKtas5UuxXZIfNTZb9w8wxeO2WnVane9S8bkmmSpIXi8ifiXdYsRH_8tIrlpVJZ2Lr-5aM_p5hR7fWwKwykHQq9F2YE3fR5Ng7MwcFAAAAB4sghZgVDXD3uanudTA6SCl6FKn

        Cookie: idsrv.xsrf=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAKna-MzOwkEGxEnjNZzRaLQAAAAACAAAAAAADZgAAwAAAABAAAABlqk1IjqrhDvmJQ9Kil_rXAAAAAASAAACgAAAAEAAAANqLSpXmx91jYBEZx3QpyTMYAAAA3n9SJ0ZSomoAaS2NQOchBEWGWk4Q8ZchFAAAAKlH25jNrAuknymgvk1qUbrjJoB5

    + Body

        username=colin&password=password&idsrv.xsrf=MzOwkEGxEnjNZzRaLQAAAAACAAAAAAADZgAAwAAAABAAAAB1X6PxhHhUL5qahWZy4G86AAAAAASAAACgAAAAEAAAAJnpFCk9aNKcfwg4WIHgeyQYAAAAMLvP4ORXNAJmIuHyGwJzk3D7QhIVVmqKFAAAALUCy2tKYHbZZ7J4FSPuzWYfftlK

+ Response 302

    + Headers

        Location: https://idp/connect/authorize?client_id=chw_implicit&response_type=id_token token&scope=openid profile special_access&state=194536517cc14866b3df079cc5cd7ee5&redirect_uri=https:%2F%2Flocalhost%2FFoo&nonce=ea98658139bb46b1969fb82a93ac0e04

        Set-Cookie: idsrv=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAKna-MzOwkEGxEnjNZzRaLQAAAAACAAAAAAADZgAAwAAAABAAAAAeK3IlaZ1CmPwPjCGgSLQfAAAAAASAAACgAAAAEAAAAPNcp3XsTfSXHi5AEHyjT-fQAAAA1PHCZ7rge3B7CO_U6B_c3TkcHe5TD6MbXCjnjb7GlTDpF2mR3g7AFGu-tZQ-5nElKFB6LEHVeSL0UauUSxA-jsl_KARJzI1tbsM_izBANoJXZ9JEgZ9kQIa3fZMslZ9syXqcE7yeg7FZD14lYPjXohHHvIpr46t0KHO7CmtKwiWB9TMHze5I4foE7_rcOitEXDPzA0U0pOunzgCCI7Amkswpqihp1I2ueo4qASF8xyiad6a0JCN7ybYYdSsPHE6iaE7r0Nesbizmv_UY6hbk5RQAAAA21cOAGoVaSMrNQJ9cSuLT38JyhA

Again, another internal redirect to the IdP authorise url.

Complete AuthN and get tokens

AuthN completes supplying the client with an id_token and access_token.

# GET https://idp/connect/authorize?client_id=chw_implicit&response_type=id_token token&scope=openid profile special_access&state=194536517cc14866b3df079cc5cd7ee5&redirect_uri=https:%2F%2Flocalhost%2FFoo&nonce=ea98658139bb46b1969fb82a93ac0e04

+ Request

    + Headers

        Cookie: idsrv.xsrf=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAKna-MzOwkEGxEnjNZzRaLQAAAAACAAAAAAADZgAAwAAAABAAAABlqk1IjqrhDvmJQ9Kil_rXAAAAAASAAACgAAAAEAAAANqLSpXmx91jYBEZx3QpyTMYAAAA3n9SJ0ZSomoAaS2NQOchBEWGWk4Q8ZchFAAAAKlH25jNrAuknymgvk1qUbrjJoB5

        Cookie: idsrv=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAKna-MzOwkEGxEnjNZzRaLQAAAAACAAAAAAADZgAAwAAAABAAAAAeK3IlaZ1CmPwPjCGgSLQfAAAAAASAAACgAAAAEAAAAPNcp3XsTfSXHi5AEHyjT-fQAAAA1PHCZ7rge3B7CO_U6B_c3TkcHe5TD6MbXCjnjb7GlTDpF2mR3g7AFGu-tZQ-5nElKFB6LEHVeSL0UauUSxA-jsl_KARJzI1tbsM_izBANoJXZ9JEgZ9kQIa3fZMslZ9syXqcE7yeg7FZD14lYPjXohHHvIpr46t0KHO7CmtKwiWB9TMHze5I4foE7_rcOitEXDPzA0U0pOunzgCCI7Amkswpqihp1I2ueo4qASF8xyiad6a0JCN7ybYYdSsPHE6iaE7r0Nesbizmv_UY6hbk5RQAAAA21cOAGoVaSMrNQJ9cSuLT38JyhA

+ Response 302

    + Headers

        Location: https://localhost/Foo#id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJub25jZSI6ImVhOTg2NTgxMzliYjQ2YjE5NjlmYjgyYTkzYWMwZTA0IiwiaWF0IjoxNDQxOTYxNjUzLCJhdF9oYXNoIjoiYWhLZEpZT1NoZGpxeEZtdGJwNHg3QSIsInN1YiI6IjEyMzQ1IiwiYW1yIjoicGFzc3dvcmQiLCJhdXRoX3RpbWUiOjE0NDE5NjE0NjMsImlkcCI6Imlkc3J2IiwiaXNzIjoiaHR0cHM6Ly9pZHNydjMuY29tIiwiYXVkIjoiY2h3X2ltcGxpY2l0IiwiZXhwIjoxNDQxOTYxOTUzLCJuYmYiOjE0NDE5NjE2NTN9.HlLpQADSZc1VyxVIQeX5IKlQu4ASYb-koCrMTFJFqndsMnte6HoP-Qc_wEYaWRpkI6GyHzzN4yBknjnzxcMCnJJ8Va71g-bLqYMD9nEbztOooN6rZPiA1QLsHYEKU8MidEo2_yv4kGDe7xIDZZC9lge5VqjxelP1wrYfjaxBoF32L9LOUo7y2dyVsRhRwox3tvNezPS7iq4cK4nduqJ7dCswyAlKcNzjFc6OFKloyUIpifk_h12UfQlZFIr74M2rulDTfvv0n3RlpnC10PZPyjYnkrRoz8ZUIiK_EyFnD0pTWegPOhNEEjIvOcshfq1RYELV4Fx1riCM4UK7kI5VAQ&access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJjbGllbnRfaWQiOiJjaHdfaW1wbGljaXQiLCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIiwic3BlY2lhbF9hY2Nlc3MiXSwic3ViIjoiMTIzNDUiLCJhbXIiOiJwYXNzd29yZCIsImF1dGhfdGltZSI6MTQ0MTk2MTQ2MywiaWRwIjoiaWRzcnYiLCJzcGVjaWFsMTAxIjoiYWRtaW4iLCJpc3MiOiJodHRwczovL2lkc3J2My5jb20iLCJhdWQiOiJodHRwczovL2lkc3J2My5jb20vcmVzb3VyY2VzIiwiZXhwIjoxNDQxOTYyMDEyLCJuYmYiOjE0NDE5NjE2NTJ9.Wuu0k9Unvbsv8MFsHKtupf5fM9567ZO9f7rcTHeMWsUhp2H781je04iUFqkblrck2uWlzomAeI3qbrjUzmAUZ3Y_7PXaZMts-Z_bNFuECYiKba9CJnhHsw6cARGtIEOVxO25z8eCMiC_Fx87eng-xm7SyQctpKRb82musZwjDWOAJNSIpzeG2JFuoURSE6TdMi7-Vonot2qKr1UaHZ1uxLp5c_jmzLKhQYhKiWeqAsOAkIQw-XBtJR8XIMnVCwkN3HxweEIVNJ-01b0uCm-Ml37vDN5S6TuODoBahTVAKaiulph-HAzngXTxod-2rrRUecf1CuLXeWi57QxucfU9Og&token_type=Bearer&expires_in=360&scope=openid%20profile%20special_access&state=194536517cc14866b3df079cc5cd7ee5

As you can see, the id_token and access_token are returned in the Url.
Information after the hash fragment identifier is not seen by the web server.

id_token JWT payload
{
  "nonce": "ea98658139bb46b1969fb82a93ac0e04",
  "iat": 1441961653,
  "at_hash": "ahKdJYOShdjqxFmtbp4x7A",
  "sub": "12345",
  "amr": "password",
  "auth_time": 1441961463,
  "idp": "idsrv",
  "iss": "https://idsrv3.com",
  "aud": "chw_implicit",
  "exp": 1441961953,
  "nbf": 1441961653
}
access_token JWT payload
{
  "client_id": "chw_implicit",
  "scope": [
    "openid",
    "profile",
    "special_access"
  ],
  "sub": "12345",
  "amr": "password",
  "auth_time": 1441961463,
  "idp": "idsrv",
  "special101": "admin",
  "iss": "https://idsrv3.com",
  "aud": "https://idsrv3.com/resources",
  "exp": 1441962012,
  "nbf": 1441961652
}

Authorization Code Grant

An authorization code is returned which is used in an exchange for an Id Token and Access Token.
See Section 3.1 of OpenId Connect.

The flow appears to similar to Implicit flow browser-wise until post AuthN.

Visit https://localhost/Foo

As a consumer, using your web browser, you navigate to a wonderful application called Foo. The browser is redirected to the Idp login page where you must enter your credentials.

# GET https://localhost/Foo

+ Response 302

    + Headers

        Location: https://idp/connect/authorize?client_id=chw_code&response_type=code&scope=openid profile special_access&state=90280f52b51f44d4ac94f9be589495fe&redirect_uri=https%3a%2f%2flocalhost%2fFoo&nonce=81c74d44e0654050b8846a66911ecefb

IdentityServer will respond with a redirect to a signin resource.

# GET https://idp/connect/authorize?client_id=chw_code&response_type=code&scope=openid profile special_access&state=90280f52b51f44d4ac94f9be589495fe&redirect_uri=https%3a%2f%2flocalhost%2fFoo&nonce=81c74d44e0654050b8846a66911ecefb

+ Response

    + Headers

        Location: https://idp/login?signin=3975a99937438d3ccecc1f2083265c19

        Set-Cookie: SignInMessage.3975a99937438d3ccecc1f2083265c19=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAsud-IOnwa0WhjBMAteN-dwAAAAACAAAAAAAQZgAAAAEAACAAAABvx82LDWmiPMfHHntabmRe_xsW2q40b30CPjiNMbSx4wAAAAAOgAAAAAIAACAAAACFouE5OaLcf5zKJDLCj_yO0K-KgiJ72wkJMKa1Oi_fnkABAACPW1kEIaliK1P3jNVcDl3NwN0KUnDzmsUG-JTzLSzCYwhIQ-2HK6-mRQO45BHAVaGeBEjafY0XZDx4sdSga6D68rDEkdRs-JorDavnZGOLJS2JQEewCiRoRIBYkIi0DboYZioleu3jnX913WOKrD3VzOCs2VrpUIwyaJxLeM0utBtsUf9cM1wIzyuGvaRytde_5_zy5xpApGckf9xf2xjb-7HBFcZi52aLozxfvBgcnhmYj3aJc9BXTD86tqY0aTkXaJS1djKI1MQI9BJgrqI9iJrBg1XI_YOpx6hTs9lUus1WbJkD1TEepPClIPf9GdpNiVVik77V6ngSGQiOBLtRismAlNt_UYA55ab1lh5WUPbtel3d1Bw7foBosFs_ghRgr_5jpzkqOqBpJf-047WlMH81Q19JwjLZyw3qEJeZXkAAAACLRfUNpAwguGT76nH61yu6hElb1D2CPNsNWyWzIeTCPubWqOx90XxV3GBfh6XVn8Kj9puPT6ChE_KrrLGmrXh0

The browser is redirected to the sign resource specific in the Location header of the last response.

User is presented with a login page /signin

The user will be presented with a login page where they input their username and password.

# GET https://idp/login?signin=3975a99937438d3ccecc1f2083265c19

+ Request

    + Headers

        Cookie: SignInMessage.3975a99937438d3ccecc1f2083265c19=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAsud-IOnwa0WhjBMAteN-dwAAAAACAAAAAAAQZgAAAAEAACAAAABvx82LDWmiPMfHHntabmRe_xsW2q40b30CPjiNMbSx4wAAAAAOgAAAAAIAACAAAACFouE5OaLcf5zKJDLCj_yO0K-KgiJ72wkJMKa1Oi_fnkABAACPW1kEIaliK1P3jNVcDl3NwN0KUnDzmsUG-JTzLSzCYwhIQ-2HK6-mRQO45BHAVaGeBEjafY0XZDx4sdSga6D68rDEkdRs-JorDavnZGOLJS2JQEewCiRoRIBYkIi0DboYZioleu3jnX913WOKrD3VzOCs2VrpUIwyaJxLeM0utBtsUf9cM1wIzyuGvaRytde_5_zy5xpApGckf9xf2xjb-7HBFcZi52aLozxfvBgcnhmYj3aJc9BXTD86tqY0aTkXaJS1djKI1MQI9BJgrqI9iJrBg1XI_YOpx6hTs9lUus1WbJkD1TEepPClIPf9GdpNiVVik77V6ngSGQiOBLtRismAlNt_UYA55ab1lh5WUPbtel3d1Bw7foBosFs_ghRgr_5jpzkqOqBpJf-047WlMH81Q19JwjLZyw3qEJeZXkAAAACLRfUNpAwguGT76nH61yu6hElb1D2CPNsNWyWzIeTCPubWqOx90XxV3GBfh6XVn8Kj9puPT6ChE_KrrLGmrXh0

+ Response 200 (text/html)

    + Body

        <!DOCTYPE html>
        <html ng-app="app" ng-controller="LayoutCtrl">
        <head>
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <title>Thinktecture IdentityServer v3 - preview 1 (SelfHost)</title>
            <link href='/assets/styles.min.css' rel='stylesheet'>

        </head>
        <body lang="en">
            <div class="navbar navbar-inverse navbar-fixed-top">
                <div class="navbar-header">
                    <a href="">
                        <span class="navbar-brand">Thinktecture IdentityServer v3 - preview 1 (SelfHost)</span>
                    </a>
                </div>
                <ul class="nav navbar-nav" ng-show="model.currentUser" ng-cloak>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">{{model.currentUser}} <b class="caret"></b></a>
                        <ul class="dropdown-menu">
                            <li><a href="{{model.logoutUrl}}">Logout</a></li>
                            <li class="divider" ng-show="model.loginWithDifferentAccountUrl"></li>
                            <li><a href="{{model.loginWithDifferentAccountUrl}}" ng-show="model.loginWithDifferentAccountUrl">Login With Different Account</a></li>
                        </ul>
                    </li>
                </ul>
            </div>

            <div class='container page-login' ng-cloak>
                <div class="page-header">
            <h1>Login</h1>
        </div>

        <div class="row" ng-show="model.errorMessage">
            <div class="col-md-12 col-sm-12">
                <div ng-show="model.errorMessage" class="alert alert-danger">
                    <strong>Error:</strong>
                    {{model.errorMessage}}
                </div>
            </div>
        </div>

        <div class="row">
            <div class="col-md-6 col-sm-6" ng-show="model.loginUrl">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h3 class="panel-title">Local Login</h3>
                    </div>
                    <div class="panel-body">
                        <form name="form" method="post" action="{{model.loginUrl}}">
                            <anti-forgery-token token="model.antiForgery"></anti-forgery-token>

                            <fieldset>
                                <div class="form-group">
                                    <label for="username">Username</label>
                                    <input required name="username" autofocus id="username" type="text" class="form-control" placeholder="Username" ng-model="model.username" maxlength="100">
                                </div>
                                <div class="form-group">
                                    <label for="password">Password</label>
                                    <input required id="password" name="password" type="password" class="form-control" placeholder="Password" ng-model="model.password" maxlength="100" autocomplete="off">
                                </div>
                                <div class="form-group login-remember" ng-show="model.allowRememberMe">
                                    <label for="rememberMe">
                                        <input type="checkbox" id="rememberMe" name="rememberMe" ng-model="model.rememberMe" value="true">
                                        <strong>Remember My Login</strong>
                                    </label>
                                </div>
                                <div class="form-group">
                                    <button class="btn btn-primary">Login</button>
                                </div>
                            </fieldset>
                        </form>
                    </div>
                    <ul class="list-unstyled">
                        <li ng-repeat="link in model.additionalLinks"><a ng-href="{{link.href}}">{{link.text}}</a></li>
                    </ul>
                </div>
            </div>

            <div class="col-md-6 col-sm-6 external-providers" ng-show="model.externalProviders">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h3 class="panel-title">External Login</h3>
                    </div>
                    <div class="panel-body">
                        <ul class="list-inline">
                            <li ng-repeat="provider in model.externalProviders">
                                <a class="btn btn-default" href="{{provider.href}}">{{provider.text}}</a>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>

            </div>

            <script id='modelJson' type='application/json'>{&quot;loginUrl&quot;:&quot;/login?signin=3975a99937438d3ccecc1f2083265c19&quot;,&quot;antiForgery&quot;:{&quot;name&quot;:&quot;idsrv.xsrf&quot;,&quot;value&quot;:&quot;AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAsud-IOnwa0WhjBMAteN-dwAAAAACAAAAAAAQZgAAAAEAACAAAAAGZZD1vGE5KG9Dc8Jn29N1Ad892Oq1xxplrVvd6lOJ1gAAAAAOgAAAAAIAACAAAABWv3DlyiXZ_ZgHKlLY3Jr1Jr2n4AejYlFNXgSUVP2YgyAAAACi-ytjbmzUlRMeKWZ4IC0Uf5fH-Hiv7p1ZQEsELFC49EAAAADrt7Zq9AngKr-x41832vRvgvuVpTPtXf8Nx6w_GKsewraxA4X0IgDnflv0OKU5zWzY7THQ5cw5PXz3ppl-REmn&quot;},&quot;allowRememberMe&quot;:true,&quot;rememberMe&quot;:false,&quot;username&quot;:null,&quot;externalProviders&quot;:[],&quot;additionalLinks&quot;:null,&quot;errorMessage&quot;:null,&quot;requestId&quot;:&quot;315fad26-efca-4492-9fb5-8de82de530f9&quot;,&quot;siteUrl&quot;:&quot;http://idp/&quot;,&quot;siteName&quot;:&quot;Thinktecture IdentityServer v3 - preview 1 (SelfHost)&quot;,&quot;currentUser&quot;:null,&quot;logoutUrl&quot;:&quot;http://idp/logout&quot;}</script>
            <script src="/assets/scripts.js"></script>

        </body>
        </html>

User credentials are POSTed

The user will login.

# POST https://idp/login?signin=3975a99937438d3ccecc1f2083265c19

+ Request (application/x-www-form-urlencoded)

    + Headers

        Cookie: SignInMessage.3975a99937438d3ccecc1f2083265c19=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAsud-IOnwa0WhjBMAteN-dwAAAAACAAAAAAAQZgAAAAEAACAAAABvx82LDWmiPMfHHntabmRe_xsW2q40b30CPjiNMbSx4wAAAAAOgAAAAAIAACAAAACFouE5OaLcf5zKJDLCj_yO0K-KgiJ72wkJMKa1Oi_fnkABAACPW1kEIaliK1P3jNVcDl3NwN0KUnDzmsUG-JTzLSzCYwhIQ-2HK6-mRQO45BHAVaGeBEjafY0XZDx4sdSga6D68rDEkdRs-JorDavnZGOLJS2JQEewCiRoRIBYkIi0DboYZioleu3jnX913WOKrD3VzOCs2VrpUIwyaJxLeM0utBtsUf9cM1wIzyuGvaRytde_5_zy5xpApGckf9xf2xjb-7HBFcZi52aLozxfvBgcnhmYj3aJc9BXTD86tqY0aTkXaJS1djKI1MQI9BJgrqI9iJrBg1XI_YOpx6hTs9lUus1WbJkD1TEepPClIPf9GdpNiVVik77V6ngSGQiOBLtRismAlNt_UYA55ab1lh5WUPbtel3d1Bw7foBosFs_ghRgr_5jpzkqOqBpJf-047WlMH81Q19JwjLZyw3qEJeZXkAAAACLRfUNpAwguGT76nH61yu6hElb1D2CPNsNWyWzIeTCPubWqOx90XxV3GBfh6XVn8Kj9puPT6ChE_KrrLGmrXh0

        Cookie: idsrv.xsrf=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAsud-IOnwa0WhjBMAteN-dwAAAAACAAAAAAAQZgAAAAEAACAAAADDA99JrLG_fe-OpwHFmw9H5w8Aajc5So9BfifsGzIVnQAAAAAOgAAAAAIAACAAAADL0wutHbG_Y68BYn-7BvTz8T_0VdNjDcxg1cb6rtSG2iAAAABdZGpczbiNI-GD6lH8EKfkRn9Zu9RGnOERWjiv_Hd1IUAAAACrL3MyLHzG2KGUnnwloOlYpsC1uMq4Hbv3ugiWBs66SPCTzCCJPBmXiJd_WmKq9zyRCsY10al-w_GcZwRa3Lll

    + Body

        username=colin&password=password&idsrv.xsrf=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAsud-IOnwa0WhjBMAteN-dwAAAAACAAAAAAAQZgAAAAEAACAAAABvx82LDWmiPMfHHntabmRe_xsW2q40b30CPjiNMbSx4wAAAAAOgAAAAAIAACAAAACFouE5OaLcf5zKJDLCj_yO0K-KgiJ72wkJMKa1Oi_fnkABAACPW1kEIaliK1P3jNVcDl3NwN0KUnDzmsUG-JTzLSzCYwhIQ-2HK6-mRQO45BHAVaGeBEjafY0XZDx4sdSga6D68rDEkdRs-JorDavnZGOLJS2JQEewCiRoRIBYkIi0DboYZioleu3jnX913WOKrD3VzOCs2VrpUIwyaJxLeM0utBtsUf9cM1wIzyuGvaRytde_5_zy5xpApGckf9xf2xjb-7HBFcZi52aLozxfvBgcnhmYj3aJc9BXTD86tqY0aTkXaJS1djKI1MQI9BJgrqI9iJrBg1XI_YOpx6hTs9lUus1WbJkD1TEepPClIPf9GdpNiVVik77V6ngSGQiOBLtRismAlNt_UYA55ab1lh5WUPbtel3d1Bw7foBosFs_ghRgr_5jpzkqOqBpJf-047WlMH81Q19JwjLZyw3qEJeZXkAAAACLRfUNpAwguGT76nH61yu6hElb1D2CPNsNWyWzIeTCPubWqOx90XxV3GBfh6XVn8Kj9puPT6ChE_KrrLGmrXh0

+ Response 302

    + Headers

        Location: https://idp/connect/authorize?client_id=chw_code&response_type=code&scope=openid profile special_access&state=90280f52b51f44d4ac94f9be589495fe&redirect_uri=https:%2F%2Flocalhost%2FFoo&nonce=81c74d44e0654050b8846a66911ecefb

A redirect is returned.

Browser is redirected to /authorize

# GET https://idp/connect/authorize?client_id=chw_code&response_type=code&scope=openid%20profile%20special_access&state=90280f52b51f44d4ac94f9be589495fe&redirect_uri=https%3A%2F%2Flocalhost%2FFoo&nonce=81c74d44e0654050b8846a66911ecefb

+ Request

    + Headers

        Cookie: idsrv.xsrf=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAsud-IOnwa0WhjBMAteN-dwAAAAACAAAAAAAQZgAAAAEAACAAAADDA99JrLG_fe-OpwHFmw9H5w8Aajc5So9BfifsGzIVnQAAAAAOgAAAAAIAACAAAADL0wutHbG_Y68BYn-7BvTz8T_0VdNjDcxg1cb6rtSG2iAAAABdZGpczbiNI-GD6lH8EKfkRn9Zu9RGnOERWjiv_Hd1IUAAAACrL3MyLHzG2KGUnnwloOlYpsC1uMq4Hbv3ugiWBs66SPCTzCCJPBmXiJd_WmKq9zyRCsY10al-w_GcZwRa3Lll

        Cookie: idsrv=AQAAANCMnd8BFdERjHoAwE_Cl-sBAAAAsud-IOnwa0WhjBMAteN-dwAAAAACAAAAAAAQZgAAAAEAACAAAABMBloJW4qcdkYmoY5NbNFV21JvDy4r5NklInrzQwmRvAAAAAAOgAAAAAIAACAAAACnrBnshrAO4U7-8NL0QZHPD_fd_SPfOsRNBwbcZ9m-ItAAAABU7G00H6QAEdjh0o4a4EPgfC9KBNIKGppUw65sL56QVZTwmLlIqzlHXj3OdQLp4KSc6szoT07a6pjGvRbPvG4bnFycUgsbE017TQ5mZif7Ja0VlKvsXTC6324nrTrfPHsiD5IoJjWJungN-JaQWhrADSks7Y9YIm5oe4QvMGvrds7YBYfWdpUdiOeWvvgJcTC2Fyq4nYkEwyS759nznSIq418NBTouzyY5bvV4M_hxN29kDy9FgbwAmF4Qk8rKuXnmr2kYRy-mh2C0HVX8DhjXQAAAAN6L1pytlk0g3dEMH6Gbigkhy-sZDIwgksk1tBptQ8G6mpZgcdDx-jaDquIpqnMDPEYB9Zy-rx-T3PH9ci_MFas

+ Response 302

    + Headers

        Location: https://localhost/Foo?code=19afd8e51c3bfb35fbd96ad702d3abe8&state=90280f52b51f44d4ac94f9be589495fe

Code is made available to server

Server requests tokens using the code from the location Uri.

# POST https://idp/connect/token

+ Request (application/x-www-form-urlencoded)

    + Headers

        Authorization: Basic Y2h3X2NvZGU6c2VjcmV0

    + Body

        grant_type=authorization_code&code=19afd8e51c3bfb35fbd96ad702d3abe8&redirect_uri=https://localhost/Foo

+ Response 200 (application/json)

    + Body

        {
            "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJub25jZSI6IjgxYzc0ZDQ0ZTA2NTQwNTBiODg0NmE2NjkxMWVjZWZiIiwiaWF0IjoxNDQyMzk4MTg4LCJzdWIiOiIxMjM0NSIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDQyMzk3ODU2LCJpZHAiOiJpZHNydiIsImlzcyI6Imh0dHBzOi8vaWRzcnYzLmNvbSIsImF1ZCI6ImNod19jb2RlIiwiZXhwIjoxNDQyMzk4NDg4LCJuYmYiOjE0NDIzOTgxODh9.F8D18Oei7IOcz4jSpIOYgpoRPn9GpvWXQfQT4yReZUZToQVPZand2msU0vtNyl_Y1hUuRpqCFgzEi33v3P3RmZndrLj0YW0M_Hvmui9insJkF5o3zvv2c0I1b4rp97OgQzmuWNE-gMUYJihflT1f4xAH74i__ZsiQPLfoEiJxGlJQ5NdBRyRVZWG31CgrjSjKxGDn_va1xdR1DoyBI24CZ5VG7xhoEgVl2D159uhnfnet5K0hqXysPEehg6juRYZUr6nOO2W3Wzl2h69cga_FRk-xlXf8MhIPPCR2zNCAM-UUnigYUh12zlEZQNPgEqKJX-Ueh6e7z5Bg7S8XQPC2A",
            "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJjbGllbnRfaWQiOiJjaHdfY29kZSIsInNjb3BlIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJzcGVjaWFsX2FjY2VzcyJdLCJzdWIiOiIxMjM0NSIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDQyMzk3ODU2LCJpZHAiOiJpZHNydiIsInNwZWNpYWwxMDEiOiJhZG1pbiIsImlzcyI6Imh0dHBzOi8vaWRzcnYzLmNvbSIsImF1ZCI6Imh0dHBzOi8vaWRzcnYzLmNvbS9yZXNvdXJjZXMiLCJleHAiOjE0NDIzOTg1NDgsIm5iZiI6MTQ0MjM5ODE4OH0.EaJuo7RYxs7CdpYQ-0i56qN-JcKa7qjcHczmmSw2nBPJX6PtT0fduA0AiI3aCk0pg856SfzsDjuW0OQCkdbkn5NVKQKmTC2FcbPeXQKRH3uH65xYymX3s1vjs_Xzzwyrx67Ftt6U-myRBLiuF86p6nHDsrK8VZbXb1ZPRRP3h3cQEWju1B4QAGT_xGZCKeZd-Z-gxv2th0t3VGJGlk2DoXnnjTw6vqc-wjL7D728nFfnm2s9mqecqUAvHzS5PXNTFgyrKTu1NWs5Hyc3JMhm3MXLHX6vbSWzYviAoEBTlMMme3k7vS74wRNQddI7VgqlkE3mcZzUjUKqgGNuiw6RNw",
            "expires_in": 360,
            "token_type": "Bearer"
        }
id_token JWT payload
{
  "nonce": "81c74d44e0654050b8846a66911ecefb",
  "iat": 1442398188,
  "sub": "12345",
  "amr": "password",
  "auth_time": 1442397856,
  "idp": "idsrv",
  "iss": "https://idsrv3.com",
  "aud": "chw_code",
  "exp": 1442398488,
  "nbf": 1442398188
}
access_token JWT payload
{
  "client_id": "chw_code",
  "scope": [
    "openid",
    "profile",
    "special_access"
  ],
  "sub": "12345",
  "amr": "password",
  "auth_time": 1442397856,
  "idp": "idsrv",
  "special101": "admin",
  "iss": "https://idsrv3.com",
  "aud": "https://idsrv3.com/resources",
  "exp": 1442398548,
  "nbf": 1442398188
}
.NET AuthN AuthZ Frameworks oAuth OpenId-Connect-1.0 OWIN Testing XUnit