Anyone connect their client app with Umbraco Membership (Headless)?
m
Using Umbraco for the back office and membership only… I’m wondering if anyone has working knowledge of how a separate client side app could authenticate a user against the Umbraco membership / auth pipeline. Sorry I’m in my phone or I’d try to be more explicit.
Nobody has used Umbraco Membership externally with a different client serving the FED? Mobile app? Anything?
p
So are you thinking if an OAuth situation where Umbraco provides the user login information?
m
Exactly that @protherj 🙂
w
In the past I've setup Identity Server in Umbraco to do this, but the open source version of it is no longer maintained. OpenIddict would be the next open source choice but does require more manual setup for endpoints etc. I've not properly looked at the OpenIddict configuration that is now present for handling authentication when using the Delivery APIs and whether it is possible to add extra clients without any issues. So don't know if it will be possible to make use of the same endpoints that are already configured with the Delivery API. But it looks like you could register a client with the OpenIddict manager
IOpenIddictApplicationManager
The default frontend member client is configured here https://github.com/umbraco/Umbraco-CMS/blob/3ce2e97f5e13de0f4e69ff8943c66744d3103b27/src/Umbraco.Cms.Api.Delivery/Security/MemberApplicationManager.cs#L35 And it is added to OpenIddict inside an application starting notification https://github.com/umbraco/Umbraco-CMS/blob/3ce2e97f5e13de0f4e69ff8943c66744d3103b27/src/Umbraco.Cms.Api.Delivery/Handlers/InitializeMemberApplicationNotificationHandler.cs#L63
p
I don't think we have done it in that direction with SSO or anything, but if I recall, I'll jump back in the thread
a
Umbraco content delivery api adds OpenIddict - which you should be able to setup to add additional clients and configuration for. Then you would be able to authenticate against it with your external application. I've not done it that way round myself - I normally have to setup Umbraco members to auto-create from a separate identity sytem - but I've used OpenIddict extensively in the past.
m
Thanks, everyone, for your insight and help last month. I hate to drag up an old topic, but I'm almost there. Last night, I had some time to experiment with Umbraco's OpenIddict / protected content API. Getting the client application set up correctly has been a hell of a battle. My inexperience with Auth.js is mostly to blame. There are a lot of sneaky caveats, and while I'm reading the documentation for both Umbraco and Auth.js, I'm not putting 2 and 2 together. I'm sure it's more of a me problem than their documentation. I'll share my Auth.js configuration below. I can successfully navigate to the Umbraco site, login, and be redirected back to my application. The challenge works now too. The only issue, and again this may be more Auth.js configuration, is how to get the profile details.
Auth.js Configuration
Copy code
export default defineConfig({
    secret: "pussyhair",
    providers: [
        {
            id: "umbraco",
            name: "Umbraco",
            type: "oidc",// oidc or oauth?
            issuer: "https://localhost:44336/",
            clientId: "umbraco-member",
            token: "https://localhost:44336/umbraco/delivery/api/v1/security/member/token",
            authorization: {
                params: {
                    scope: "openid",
                    response_mode: "query"
                }
            },
            checks: ["pkce", "state"],
            client: {
                token_endpoint_auth_method: "none"
            },
            profile(profile) {
                console.log(profile); // I'm here, and this is not a profile!
                return {
                    id: profile.sub,
                    name: profile.name,
                    email: profile.email,
                    image: profile.picture,
                };
            },
        },
    ],
    cookies: {
        pkceCodeVerifier: {
            options: {
                httpOnly: true,
                sameSite: false,
                path: "/",
                secure: false,
            },
        },
    }
});
Some things I've recently learned. Response Mode the value
form_post
from the docs didn't work, it has to be
query
. Token Auth.js is not smart enough to differentiate between a user endpoint and a token endpoint. If you don't supply this endpoint specifically, it will error because it cannot find the user endpoint. Client Secret actually matters to most client applications. If you provide a string here, even if empty, it blew up Auth.js and failed the challenge check at the end. The key here is to find the right configuration values so Auth.js doesn't force you to provide a client secret AT ALL.
I suppose I'll have to write my own API controller to return the member's basic profile info (id, name, email, image) at this point. I don't think a user endpoint is included by default, and by design.
For now, something like the API controller below has gotten me user details back:
Copy code
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using OpenIddict.Validation.AspNetCore;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Web.Common.Controllers;

namespace Humble.Umbraco_13.Website;

public class MemberController : UmbracoApiController
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public MemberController(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    
    // /Umbraco/Api/Member/Profile
    public async Task<object> Profile()
    {
        ClaimsPrincipal? requestPrincipal = await GetRequestPrincipal();
        var claims = requestPrincipal?.Claims;
        var identity = requestPrincipal?.Identity;
        return new
        {
            id = claims?.FirstOrDefault(x => x.Type.Equals("sub"))?.Value,
            name = claims?.FirstOrDefault(x => x.Type.Equals(ClaimTypes.Name))?.Value,
            email = claims?.FirstOrDefault(x => x.Type.Equals(ClaimTypes.Email))?.Value,
            image = claims?.FirstOrDefault(x => x.Type.Equals("picture"))?.Value
        };
    }
    
    /// <summary>
    /// Ref: https://github.com/umbraco/Umbraco-CMS/blob/contrib/src/Umbraco.Cms.Api.Delivery/Services/RequestMemberAccessService.cs#L39
    /// </summary>
    /// <returns></returns>
    private async Task<ClaimsPrincipal?> GetRequestPrincipal()
    {
        HttpContext httpContext = _httpContextAccessor.GetRequiredHttpContext();
        
        // Local calls
        var user = httpContext.User;
        if (user.Identity.IsAuthenticated) 
            return user;
        
        // Remote calls
        AuthenticateResult result = await httpContext.AuthenticateAsync(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
        return result.Succeeded
            ? result.Principal
            : null;
    }
}
I need to investigate if I can add "claims" to a member from their member profile. Like "picture".
a
If the data for the member is coming from the standard umbraco membership area, then don't even bother with trying to retrieve them from the claims. Just write a normal Member-authorized API controller that uses the member service to retrieve whatever profile data you want. Leave all the claim handling, authorization, etc to the underlying system. We have do similar things for multiple other mobile apps that use OAuth. You let the Auth library handle the token swap, and then before you lift the Auth-gate and let them into your app you make a call to your internal user-profile api. That API can return not just profile data, but also any other startup data your app needs on each startup (like menus, preferences for notifications handlers, etc). It can also write back to the db with analytics or last logon time, etc.
b
I'm in the same situation. With U13 I created a controller that give me a token but and authorize the API. How to with U14 that already has openidc support? How you solve the problem? Thanks.
35 Views