OpenID Connect Hybrid Flow for calling resource API (OIDC Part 4)

Share on facebook
Share on google
Share on twitter
Share on linkedin

In the last post we created an authorization code client, enabling the client to get the user claims from the id token, exchanged for the post-login authorization code. That way we were able to display the user roles on an authorized MVC view. This time, instead of getting the user roles from the userInfo endpoint directly with an access token from AppClient, we are going to get it from the resource API, using an access token.

Note: This could also have been implemented with authentication code client since we are only using an MVC client in this part. Using the hybrid client here is just for learning purpose, enabling you later to use the hybrid client both from a browser and an MVC app simultaneously.

The OpenID connect with IdentityServer4 and Angular series

This series is learning you OpenID connect with Angular with these parts:

AuthorizationServer – Setting up the hybrid client

Compared to part 3 of the OIDC series, the only thing we need to do to the AuthorizationServer is to add a hybrid client:

// OpenID Connect hybrid flow client (MVC) new Client { ClientId = "mvc", ClientName = "MVC
Client", AllowedGrantTypes = GrantTypes.Hybrid,
ClientSecrets =
{
    new Secret("secret".Sha256())
},

RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes =
{
    IdentityServerConstants.StandardScopes.OpenId,
    IdentityServerConstants.StandardScopes.Profile,
    "resourceApi"
},
AllowOfflineAccess = true
}

The only difference between this and the previous authorization code client is that browsers are here allowed to get the id token on first roundtrip. We could also get the access token on the first roundtrip if we enabled that here. With authorization code flow, they needed to exchange their authorization code for an id token.

ClientApp – Calling the resource API with access token

In part 3 we already set the AppClient up for using hybrid flow by adding the ClientSecret in the Startup authentication middleware. The only difference is here we are requesting the id_token as well as the authorization code on first request:

ClientApp/Startup.cs options.ResponseType = "code id_token";

After the user has been authenticated, the HTTPContext is going to contain an access token as well as the id token. We use this in a method for calling the resource API’s authenticated identity endpoint:

 private async Task CallApiUsingUserAccessToken() { 
    var accessToken = await HttpContext.GetTokenAsync("access_token");

var client = new HttpClient();
client.SetBearerToken(accessToken);

return await client.GetAsync("http://localhost:5001/api/identity");
}

This gets the access_token from the HttpContext, which is provided by the authentication cookie that got set on successful user authentication. After that, the access token is set and send as a bearer token. This method is used by the IdentityController.Index action:

[Authorize] public async Task Index() { 
var userClaimsVM = new UserClaimsVM(); var userClaimsWithClientCredentials = await
GetUserClaimsFromApiWithClientCredentials();
userClaimsVM.UserClaimsWithClientCredentials =
userClaimsWithClientCredentials.IsSuccessStatusCode ? await
userClaimsWithClientCredentials.Content.ReadAsStringAsync() :
userClaimsWithClientCredentials.StatusCode.ToString();

var userClaimsWithAccessToken = await CallApiUsingUserAccessToken();
userClaimsVM.UserClaimsWithAccessToken = userClaimsWithAccessToken.IsSuccessStatusCode ? await userClaimsWithAccessToken.Content.ReadAsStringAsync() : userClaimsWithAccessToken.StatusCode.ToString();

return View(userClaimsVM);
}

The user claims from the resource are stored in the userClaimsVM and the Identity/Index.cshtml should now look like this:

@model ClientApp.Models.UserClaimsVM;
@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<dl>
    <dt>
        UserClaimsWithClientCredentials
    </dt>
    <dd>
        @Model.UserClaimsWithClientCredentials
    </dd>
    <dt>
        UserClaimsWithAccessToken
    </dt>
    <dd>
        @Model.UserClaimsWithAccessToken
    </dd>
</dl>

User claims:
<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

<form asp-controller="Identity" asp-action="Logout" method="post">
    <button type="submit">Logout</button>
</form>

Now the view is also showing user claims from the resource API using access token.

No change is needed for the resource api, so we are now ready to run the servers together.

Running the app

When running the three servers all together we should be able to get an access token using the: HttpContext.GetTokenAsync("access_token").

By going to https://jwt.io/, this JWT can easily be decoded.

OpenId access token

We see that the scope is containing resourceApi as well as the audience property, allowing the user to access the ResourceApi.

When going to http://localhost:5002/identity we should now be prompted for login and consent for which we are gonna use one of the hard coded test users. Here after we are gonna be redirected to our identity view, now containing the user claims, fetched from the ResourceAPI:

Conclusion

That sums up part 4! We accomplished to setup an hybrid flow client in the Authorization Server that can return both a authorization code as well as id token and access token, if requested. Using this we obtained an access token containing the resource api as scope, which enabled our ClientApp to fetch and display authorized identity information by requesting the resource API.

Next time we are gonna make the setup more realistic by enabling user registration using ASP.NET identity.
The code for this part can be found on my Github.

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Related Posts and Comments

How I migrated my Course Platform to Analog (step by step)

Analog is a full-stack framework for Angular ala NextJS that offers server-side rendering, static site generation, and API routes. Analog empowers Angular with server rendering tools making it optimal for public websites. Otherwise, Angular has often been neglected in favor of NextJS/NuxtJS for these purposes (due to the subpar SSR/SSG experience). I recently migrated my

Read More »

The Future of Angular and the Latest Features in Angular

In this video, I’ll be discussing the future of Angular and the latest features in Angular 17, including standalone components, signals, new control flow, the deferred operator, and server-side rendering improvements. I’ll also touch on the use of Tailwind CSS for styling and the benefits it offers. Join me to learn more about the exciting

Read More »

How to Invest as a Software Developer

In this video, I share my personal investment strategy as a software developer, focusing on putting money to work and owning assets that generate cash flow and appreciation. I discuss the snowball effect of building an investment portfolio over time and the importance of compounding. I also touch on the allocation of investments in stocks,

Read More »

Angular 17: What’s new?

Angular has since the latest major version started what the Angular team calls a renaissance, which means a radical renewal of the framework. The main goals of the latest updates have been to improve the developer experience and performance so it aligns more with the other leading front-end frameworks in the space by introducing new

Read More »

How to Set up a CI pipeline with Azure Pipelines and Nx

It goes without saying that having a CI pipeline for your Angular apps is a must. Setting one up for regular Angular apps is fairly straightforward but when you have an Nx monorepo there are certain other challenges that you have to overcome to successfully orchestrate a “build once, deploy many” pipeline. This post will

Read More »