21

I try to get OpenID Connect running... A user of my Web API managed to get an Authorization Code of a OpenID Connect Provider. How am I supposed to pass this code to my ASP.NET Web API? How do I have to configure OWIN Middleware such that I can get an Access Token using the Authorization Code?

UPDATE: A SPA uses AJAX for communicating with my web service (ASP.NET Web API). In my web service a use OWIN Middleware. I set OpenIDConnect as the authentication mechanism. When the web service is called for the first time it successfully redirected the user to the login page of the OpenID Connect Provider. The user could login and got an Authorization Code as a result. AFAIK this code could now be used (by my web service) to the an Access Token. However, I don't know how to get this code back to my web service (is this done using a header?) and then what to configure to get the Access Token. I guess I could call the token endpoint manually but I would like to take advantage of the OWIN component instead.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Dunken
  • 8,481
  • 7
  • 54
  • 87
  • I'm not clear what you're trying to accomplish. Are you asking about the OWIN middleware of the Web API? Or some client app that is calling the API? – BenV Aug 12 '14 at 17:15
  • OK, I just updated my question. – Dunken Aug 12 '14 at 18:06

5 Answers5

13

BenV already answered the question, but there's more to consider.

class partial Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        // ...

        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
          {
            ClientId = clientId,
            Authority = authority,
            Notifications = new OpenIdConnectAuthenticationNotifications() {
                AuthorizationCodeReceived = (context) => {
                   string authorizationCode = context.Code;
                   // (tricky) the authorizationCode is available here to use, but...
                   return Task.FromResult(0);
                }
            }
          }
    }
}

Two problems:

  • First of all, authorizationCode will get expired quickly. There's no sense in storing it.
  • The second problem is that AuthorizationCodeReceived event will not get fired for any of the page reloads as long as authorizationCode is not expired and stored inside the session.

What you need to do is to call AcquireTokenByAuthorizationCodeAsync which will cache it and handle properly inside TokenCache.DefaultShare:

AuthorizationCodeReceived = (context) => {
    string authorizationCode = context.Code;
    AuthenticationResult tokenResult = await context.AcquireTokenByAuthorizationCodeAsync(authorizationCode, new Uri(redirectUri), credential);
    return Task.FromResult(0);
}

Now, before every call to the resource, invoke AcquireTokenSilentAsync to get the accessToken (it will use TokenCache or silently use refreshToken ). If token is expired, it will raise AdalSilentTokenAcquisitionException exception (invoke access code renew procedure).

// currentUser for ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
AuthenticationResult authResult = await context.AcquireTokenSilentAsync(resourceUri, credential, currentUser);

Calling AcquireTokenSilentAsync is very fast if token is cached.

andrew.fox
  • 7,435
  • 5
  • 52
  • 75
  • In case when calling `AcquireTokenSilentAsync` throw exception duo to `authorizationCode` is expired. What exactly to do next? Get new Authorization code? In my app, when it happened, i used `AcquireToken` and it return a fault token – Hiep Lam Oct 17 '17 at 04:36
  • @HiepLam, you need to call AcquireTokenByAuthorizationCodeAsync() first, as in the example. Calling AcquireTokenSilentAsync() is meant for further calls during the request processing, so you don't store the "authorizationCode". But, it's also possible, that you don't have permissions to the resource, hence the exception. – andrew.fox Oct 17 '17 at 05:21
  • But it need to pass the validation then the authentication server send back the `authorization code` right (`OnOpenIdAuthorizationCodeReceived` event on OWIN pipeline)?. So when the code is expired how do i get another `authorization code` ? – Hiep Lam Oct 17 '17 at 05:37
  • @HiepLam, you don't have to store `authorization code`. Calling AcquireTokenByAuthorizationCodeAsync() puts it into internal cache, and futher calls to AcquireTokenSilentAsync() use this cache internally (so you don't have to store it by yourself). – andrew.fox Oct 17 '17 at 06:33
  • I understand your idea. At first time, i call `AcquireTokenByAuthorizationCodeAsync` in `AuthorizationCodeReceived` event and everything works well. After a period of time, the `authorization code` is expired and it throw exception when calling `AcquireTokenSilentAsync` . So at that time i wonder how to get a new `authorization code`. Or Is there another way to fix that problem? – Hiep Lam Oct 17 '17 at 06:44
12

Looks like the recommended approach is to use the AuthorizationCodeReceived event to exchange the Auth code for an Access Token. Vittorio has a blog entry that outlines the overall flow.

Here's an example from this sample app on GitHub of the Startup.Auth.cs code to set this up:

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
        ClientId = clientId,
        Authority = Authority,
        Notifications = new OpenIdConnectAuthenticationNotifications()
        {
            AuthorizationCodeReceived = (context) =>
           {
               var code = context.Code;
               ClientCredential credential = new ClientCredential(clientId, appKey);
               string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
               string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
               AuthenticationContext authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantID), new EFADALTokenCache(signedInUserID));
               AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                           code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceID);

               return Task.FromResult(0);
            },
            ...
    }

Note: The AuthorizationCodeReceived event is invoked only once when authorization really takes place. If the auth code is already generated and stored, this event is not invoked. You have to logout or clear cookies to force this event to take place.

BenV
  • 12,052
  • 13
  • 64
  • 92
  • 5
    Thanks, makes sense. However, AuthorizationCodeReceived never fires in my case... I think I have to somehow pass the authorization code from the SPA to the web service... – Dunken Aug 13 '14 at 10:41
4

This is now built into Microsoft.Owin 4.1.0 or later. You can use SaveTokens to make the id_token available and then RedeemCode to get an access token and make that available

       app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    ClientId = clientId,
                    Authority = authority,
                    PostLogoutRedirectUri = postLogoutRedirectUri,
                    ClientSecret = "redacted",
                    RedirectUri = postLogoutRedirectUri,
                    //This allows multitenant
                    //https://github.com/Azure-Samples/guidance-identity-management-for-multitenant-apps/blob/master/docs/03-authentication.md
                    TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = false
                    },

                    Notifications = new OpenIdConnectAuthenticationNotifications()
                    {
                        AuthenticationFailed = (context) =>
                        {
                            return Task.FromResult(0);
                        }
                    },
                    SaveTokens = true,
                    // Required for the authorization code flow to exchange for tokens automatically
                    // using this means you will need to provide RedirectUri and ClientSecret
                    RedeemCode = true
                }
                );

You can then access the tokens through the HttpContext object

var result = Request.GetOwinContext().Authentication.AuthenticateAsync("Cookies").GetAwaiter().GetResult();
string idToken = result.Properties.Dictionary["id_token"];
string accessToken = result.Properties.Dictionary["access_token"];

Sources: How to get access token from httpcontext using owin and Mvc 5

ToDevAndBeyond
  • 1,120
  • 16
  • 24
  • first of all +1 for the solution.. this is not there in any MS documentation but worked like charm.... Now I am able to get access_token, id_token and refresh_token from 'GetOwinContext().Authentication.AuthenticateAsync("Cookies")' but problem is how to get access_token issued and expiry time from same properties ? ExpireUtC gives Id token time... but is there any code which specificlly gices access_token expiry time? – Mahajan344 Feb 19 '23 at 12:29
1

You need to bypass the default owin validation to do custom Authorization:

           new OpenIdConnectAuthenticationOptions
            {
                ...,
                TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
                {
                    ValidateIssuer = false
                },
James
  • 23
  • 3
0
TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RoleClaimType = ClaimTypes.Role },

This line of code solved my issue. We need to validate the issuer to be false.

4b0
  • 21,981
  • 30
  • 95
  • 142