Secure Angular application using OpenIddict and ASP.NET Core with BFF

The article shows how an Angular nx Standalone UI hosted in an ASP.NET Core application can be secured using cookies. OpenIddict is used as the identity provider. The trusted application is protected using the Open ID Connect code flow with a secret and using PKCE. The API calls are protected using the secure cookie and anti-forgery tokens to protect against CSRF. This architecture is also known as the Backend for Frontend (BFF) security pattern.

Code: https://github.com/damienbod/bff-openiddict-aspnetcore-angular

Architecture Setup

The application is setup to authenticate as one and remove the sensitive data from the client browser. The solutions exists of the UI logic implemented in Angular and the server logic, including the security flows, implemented in ASP.NET Core. The server part of the application handles all request from the client application and the client application should only use the APIs from the same ASP.NET Core implemented host. Cookies are used to send the secure API requests. The UI implementation is greatly simplified and the backend application can add additional security features as it is a confidential client, or trusted client.

OpenIddict Setup

The OpenIddict server is used to issue tokens using OpenID Connect. The server allows a OIDC confidential client to get tokens and to authenticate the user and the application.

await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
    ClientId = "oidc-pkce-confidential",
    ConsentType = ConsentTypes.Explicit,
    DisplayName = "OIDC confidential Code Flow PKCE",
    DisplayNames =
    {
        [CultureInfo.GetCultureInfo("fr-FR")] = "Application cliente MVC"
    },
    PostLogoutRedirectUris =
    {
        new Uri("https://localhost:5001/signout-callback-oidc")
    },
    RedirectUris =
    {
        new Uri("https://localhost:5001/signin-oidc")
    },
    ClientSecret = "oidc-pkce-confidential_secret",
    Permissions =
    {
        Permissions.Endpoints.Authorization,
        Permissions.Endpoints.Logout,
        Permissions.Endpoints.Token,
        Permissions.Endpoints.Revocation,
        Permissions.GrantTypes.AuthorizationCode,
        Permissions.GrantTypes.RefreshToken,
        Permissions.ResponseTypes.Code,
        Permissions.Scopes.Email,
        Permissions.Scopes.Profile,
        Permissions.Scopes.Roles,
        Permissions.Prefixes.Scope + "dataEventRecords"
    },
    Requirements =
    {
        Requirements.Features.ProofKeyForCodeExchange
    }
});

ASP.NET Core Setup

The ASP.NET Core application implements the OIDC confidential client. The client uses OIDC to authenticate and stores this data in a session. This is really simple and does not require anything else. It is a confidential client, which means it does must be able to keep a secret. The default ASP.NET Core AddOpenIdConnect method is used and no secret client wrappers are required to implement the client as it is standard OpenID Connect. Using standards simplifies your security and as soon as you move away from standards, you increase the complexity which is always bad and usually reduce the security.

var stsServer = configuration["OpenIDConnectSettings:Authority"];

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
    configuration.GetSection("OpenIDConnectSettings").Bind(options);
    options.Authority = configuration["OpenIDConnectSettings:Authority"];
    options.ClientId = configuration["OpenIDConnectSettings:ClientId"];
    options.ClientSecret = configuration["OpenIDConnectSettings:ClientSecret"];

    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.ResponseType = OpenIdConnectResponseType.Code;

    options.SaveTokens = true;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name"
    };
});

Angular Setup

The Angular solution for development and production is setup like described in this blog:

The UI part of the application implements no OpenID connect flows and is always part of the server application. The UI can only access APIs from the single hosting application. The Angular UI uses an interceptor to apply the CSRF and also uses the CSP from the server part of the application.

Links

https://github.com/damienbod/bff-aspnetcore-angular

https://github.com/damienbod/bff-auth0-aspnetcore-angular

https://learn.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core

https://nx.dev/getting-started/intro

https://github.com/isolutionsag/aspnet-react-bff-proxy-example

https://github.com/openiddict

5 comments

  1. […] Secure Angular application using OpenIddict and ASP.NET Core with BFF (Damien Bowden) […]

  2. Sebastian Busek · · Reply

    Hi, nice article. How does the refresh works? I would say, you have to make full page refresh to get a fresh cookie from server, am I right? How do you establish proper cookie time validity?
    Thanks, Sebastian

    1. This is handled by the server. If you refresh the server fully it will require a new cookie and a new login (delete the cookie) If you just refresh or open again, the session is normally valid on the IDP and you are re-authenticated, most users will not notice this

      Greetings Damien

      1. Sebastian Busek · ·

        Ohh, sorry for not being clear while asking.
        I meant, how does the authorization refresh work? I assume that while doing Login (full page request (not XHR) to verify login and password, generate a cookie, which is sent back to the user agent.), the cookie is valid only for a given amount of minutes/hours/days.
        Because we’re in the world of Single Page Applications, the auth cookie will eventually expire.
        How do you refresh the cookie? As far as I know, you can only make a full-page request again, so your app would lose its current state (unless you store it in local storage or something similar), which hurt the UX. Or am I missing something?
        How long is a reasonable time to set auth cookie expiration?

      2. Hi Sebastian

        great question and I am not sure 🙂 I need to investigate this. My cookies are valid for 24hrs so I never ran into this problem in any of my production deployments. The user or profile api could be used to initiate the client session, you have the same problem when you do a hard reload anyway. For 90 days a silent refresh is run and this should not happen as long as the IDP session is not killed, this is different in every IDP. The login and logout must be a full page redirect.

        I will investigate this further, thanks for the comment.

        Greetings Damien

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.