immich
immich copied to clipboard
OAuth2.0/OIDC implementation
Hi all! I was interested in implementing OAuth2.0/OIDC login in Immich as discussed in #33. I gave it a try and after all I adapted the backend to use OAuth2 in combination with the local authentication that was already implemented; here are the details for now.
Instructions removed, please take a look at this message for updated instructions
I've marked this as a draft pull request because I need to work on the mobile app and on the web interface before it's fully working. Before working on that tho, I wanted to discuss with you some topics.
First of all is the expected behaviour when migrating an already existing instance of Immich to use OAuth2. I was thinking that when an user first access to Immich with a valid OAuth2 access token, his local account would be disabled and he has to continue using the OAuth from that point on. What do you think about that?
Second is if we should keep the possibility of having both local and OAuth authentication working together at all. I kept them as most separate as possible to be fully flexible, but being very easy to fix I dismissed the problem for later.
Last one is about the admin account. For what I've seen from latest commits an admin account has been created in order to create new users and while using OAuth2 this function is not needed. Is it planned to have more admin functions so that it does make sense to implement a way of distinguishing admin accounts with OAuth (through scopes for example)?
Let me know what you think about this 😄
Firstly, thank you for opening a PR. I appreciate you spending the time to do the research and implement this feature. This PR for sure increases the quality of the app significantly
Below are my answer/question to your concerns
Note that for now the websocket authorization is not working as I have to figure out a clean way to do it.
For the WebSocket, we are also using the access token, after getting the access token from the OAuth, I assume it can still be used as the current flow when opening the socket. Can you elaborate on your concern?
First of all is the expected behaviour when migrating an already existing instance of Immich to use OAuth2. I was thinking that when a user first access to Immich with a valid OAuth2 access token, his local account would be disabled and he has to continue using the OAuth from that point on. What do you think about that?
Second is if we should keep the possibility of having both local and OAuth authentication working together at all. I kept them as most separate as possible to be fully flexible, but being very easy to fix I dismissed the problem for later.
I don't have a full understanding of how OAuth flow works, what are the pros and cons of having both OAuth properties integrate into the current user information?
Last one is about the admin account. For what I've seen from latest commits an admin account has been created in order to create new users and while using OAuth2 this function is not needed. Is it planned to have more admin functions so that it does make sense to implement a way of distinguishing admin accounts with OAuth (through scopes for example)?
The admin account will be there to administer the server. There are plans to have additional settings that can be set through the admin interface (Like enable transcoding, which set of video transcoding should the server use...etc). So I would like the admin still can control who can access the server.
Do you have any good resources for developing an OAuth system? And what OAuth that people can self-hosted, I would like to spin up one to test :)
Note
This PR https://github.com/alextran1502/immich/pull/206 would change the project of the server folder quite heavily. I suggest you either merge the PR to your branch or wait until I merge it to main
to update your branch.
For the WebSocket, we are also using the access token, after getting the access token from the OAuth, I assume it can still be used as the current flow when opening the socket. Can you elaborate on your concern?
No no, I'm not concerned, I just have to research a way to use the already implemented AuthGuard with websockets instead. I will work on that!
I don't have a full understanding of how OAuth flow works, what are the pros and cons of having both OAuth properties integrate into the current user information?
The general idea here is that with OAuth2.0/OIDC the backend doesn't have to handle authentication and authorization. For this reason once the user is logged with the token generated from your OAuth server, the backend validates that token and fully trusts what's in the payload.
So for example it would be possible to configure a user called enrico
on the OAuth server, when I log into Immich I get redirect to my OAuth server to insert user/password, after the successful login I retrieve the token to pass to Immich in the API calls. Immich now is reading that token and validating it against the OAuth server.
What I was wondering is if we want to allow an instance of Immich to use both JWT and OAuth2 together, or if we want the user to pick an authentication type and use only that.
The admin account will be there to administer the server. There are plans to have additional settings that can be set through the admin interface (Like enable transcoding, which set of video transcoding should the server use...etc). So I would like the admin still can control who can access the server.
Okay, I will work on that too!
This PR https://github.com/alextran1502/immich/pull/206 would change the project of the server folder quite heavily. I suggest you either merge the PR to your branch or wait until I merge it to main to update your branch.
Thanks for letting me know, I still have a lot to do so I will wait and merge from main
.
Do you have any good resources for developing an OAuth system?
I found auth0 to be very good to start with. It does explain all the concepts needed and it gives examples on how to implement it. Maybe you can start reading from here.
And what OAuth that people can self-hosted, I would like to spin up one to test :)
For testing I'm using Authentik which is straight forward to spin up in docker and the configuration is easy too. You just have to create a new application and a new provider associated with the application. From the provider page you can retrieve all the info you have to put in the environment variables.
Tell me if something is not clear!
Hi again, with the last commit I changed the validation flow.
To test now you should generate an id_token
in OIDCDebugger and use that in the bearer token...
It's very likely I'll need to change some config environment variable too, I will keep updated the first message with the correct intruction
This would be an awesome addition, although I don't have a self-hosted OAuth setup yet, I definitely want one for these kind of applications. To chip in on some of the questions:
What I was wondering is if we want to allow an instance of Immich to use both JWT and OAuth2 together, or if we want the user to pick an authentication type and use only that.
Without looking at any of the code, I imagine this would result in added complexity and probably isn't necessary. However, if you don't believe so, then maybe it's worthwhile having for people who want users who aren't fully fledged OAuth users. Whichever way we go here though, I think that the admin account should have the option of logging in either way, incase something with OAuth breaks and you need a backup to get into the server. This is especially necessary as longer term it would be good to move away from using environment variables for all of these config options and instead pushing them into the SQL server with all the options configurable from the admin panel.
@zackpollard I agree on the possibility of having both authentication type enabled together. The approach I followed now is that the user can enable OAuth through env var, if OAuth is enabled then it's possible to disable the local login with LOCAL_USERS_DISABLE
(or similar, I don't remember rn).
Maybe it would be better to disallow the creation of new local users but leave enabled the login for already registered ones?
I don't think moving the configuration to a SQL database would be a good choice, in particular because it would add difficulties with the provisioning of the docker container. But that's a problem for the future I guess haha
Hi all, finally the mobile app and server are ready to be used with OAuth2/OIDC. I'm sure there is some polishing to do and whole web client is not compatible yet with this authentication, but here I'm writing a step by step guide to manually test this PR.
First of all you should have your own OIDC server set up, if you don't then you can find how to setup Authentik here (you don't have to setup email and GeoIP).
Once you have Authentik setup make sure it's exposed through HTTPS (it's needed by the mobile app library that handles OAuth2, otherwise it won't work).
Then you have to setup a provider, so in Authentik go in Applications > Providers and create a new one: in the first page select OAuth2/OpenID Provider
, then in the second page give it a name and change Confidential to Public.
After that you have to create a new application in Authentik, go to Applications > Applications and create a new one: pick a name and a slug, select the provider you create just before and leave everything else unchanged.
You need to add some users, so go to Directory > Users and create all the users you want (just make sure to always enter an email). For each user you create, click Impersonate
and in the user setting change the password to the one you want.
If you want to create admin users, you should create a new group called immich_admin
and add the user to that group.
Before starting Immich make sure you add the environment variables needed:
OAUTH2_ENABLE=true
LOCAL_USERS_DISABLE=false #not needed
OAUTH2_ISSUER=https://example.com/application/o/immich/
OAUTH2_CLIENT_ID=your_oauth2_client_id
OAUTH2_CERTIFICATE=#here goes the bae64 encoded PEM certificate of your OIDC provider
You can find the issuer URL and the client ID in the provider page in Authentik, the certificate is in System > Certificates (download the certificate, base64-encode the content of the file).
That's all you need to setup to use OAuth2/OIDC, so now you can start your Immich server and open the application.
Leave the user and password blank, just enter the Immich endpoint and click login. You should be redirected to the login page of Authentik, enter there username and password.
If the login is successful then you should be redirected back to the mobile application, from there you can use everything as before.
Please try it out and let me know if you have any feedback! 😄
Hi again, so here it is Immich with full OAuth2/OIDC support. Backend, mobile app and web all support OAuth2 authentication now. The setup has slightly changed, just a different env var to set (you can find all the instruction in the message above).
In this iteration I implemented the authentication for the webapp but I've never used Svelte before, so I kindly ask you to take a look at the fantastic code I wrote. For now I can only guarantee that it works, but I feel it violates all possible best practices. So please take a look at the new code since the moment this PR could be finally reviewed to be merged is coming closer and closer! Thanks! 😄
Hey man just to let you know that I appreciate the work. I apologize there have been other issues that need to be resolved before fully setting this up to do a thorough testing.
Hi Alex, thanks for the appreciation!
Right now I'm most concerned about two things:
-
the web frontend code: I haven't used Svelte and I don't like the code I wrote, the OAuth2 part is working tho so it shouldn't be too difficult to refactor for someone who knows the framework
-
the backwards compatibility of the mobile app with the backend: as of now the authentication for the websocket has slightly changed and it's the only breaking change. For this reason I was thinking of keeping both the old and new authentications for some releases, and in a month or two remove the compatibility code to keep the new one only. What do you think?
2. the backwards compatibility of the mobile app with the backend: as of now the authentication for the websocket has slightly changed and it's the only breaking change. For this reason I was thinking of keeping both the old and new authentications for some releases, and in a month or two remove the compatibility code to keep the new one only. What do you think?
Hey, would also like to extend the thanks for working in this, it's a big piece of work to implement and will be a great addition.
As for the 2nd question, I think it's fine to release breaking changes right now. Immich is still in a period of fast development and has not hit its first major release milestone yet, so we have been releasing breaking changes without too much thought. Currently we are only really focussed on keeping the latest app version working with latest server version. Alex can confirm, but we've not been holding back on releasing breaking changes yet as maintaining two lots of compatible code will slow down feature development and push back the first initial release.
Thanks @zackpollard for the answer, I’ll keep that in mind!
Hi @alextran1502, I just merged from main all the changes. I've got to admit that it's becoming really difficult to keep up with the changes since I also changed a lot of stuff on my end. I'd like to merge as soon as possible these changes 😬
Since I'm most confident on the backend and on the flutter app, if you want I can create a new PR for those only. Then we will merge the web frontend when you have more time to check it thoroughly. Sorry for asking but it's really time consuming having to merge conflicting changes. Thanks for your comprehension! 😁
@EnricoBilla I will try to have this merge this weekend. Thank you
I've set it up and my starting point is from the web.
My networking spec is as followed
Authentik and Immich are running from a remote development machine with IP 192.168.1.216
.
Immich-web can be accessed at
http://192.168.1.216:2283
Authentik can be accessed with the two IP addresses
-
http://192.168.1.216:9000
-
https://192.168.1.216:9443
I can ping immich-server at http://192.168.1.216:2283/api/server-info/ping
Issue 1
After creating a normal user on the web and navigating to the login page. Upon clicking on the Oauth2 Login, the page stayed there and I see this in the console

And then clicking again on the button I see this

This is my .env
setup
OAUTH2_ENABLE=true
LOCAL_USERS_DISABLE=false #not needed
OAUTH2_ISSUER=https://192.168.1.216:9443/application/o/immich-oauth/
OAUTH2_CLIENT_ID=2309773c0fc9538f2dc5c290f600644dd4f37ae3
OAUTH2_CERTIFICATE=-----BEGIN CERTIFICATE----<content>-----END CERTIFICATE-----
Re: Issue 1
@alextran1502, that issue is not related to Immich but to Authentik. Please go in the Provider settings and verify that the "Redirect URIs/Origin" contains https://192.168.1.216:9443
.
The CORS error already happened to me before and it was an issue with this.
Let me know if it works!
Also note that the OAUTH2_CERTIFICATE
env var need to be base64 encoded. I'd like to use a better solution for this, but didn't find anything :(
@EnricoBilla How do I generate the base64 encoded content from the .pem file? 😅 ?
@alextran1502 on Linux you can use cat file.pem | base64
. Otherwise you can just use an online base64 encoder, the PEM certificate doesn't include a private key so no worries using external tools.
Hello @EnricoBilla
Here is some update.
I still have a problem using OAuth on the web, still, I haven't tried it on mobile yet.
Here is the error message when click on OAuth2 Login on the web at from http://192.168.1.216:2283/auth/login
Here is my application provider
Here is my .env
OAUTH2_ENABLE=true
LOCAL_USERS_DISABLE=false #not needed
OAUTH2_ISSUER=https://192.168.1.216:9443/application/o/immich-oauth/
OAUTH2_CLIENT_ID=2309773c0fc9538f2dc5c290f600644dd4f37ae3
OAUTH2_CERTIFICATE=base-64-pem-using-cat-file-|-base64
What should I do in this case?
Hi @alextran1502, are you using a self signed TLS certificate?
I guess you need one from a valid CA. I'm sorry but I didn't know about this requirement, it's a check made by the library.
Hmm I didn't use any self-signed certificate since I access it from my local network. What did you have to do resolve this issue on your setup?
I used a valid TLS certificate generated from Let's Encrypt. I use a wildcard certificate for my domain, so I don't have to expose test services on the internet. If you want I can give you privately admin access to my test Authentik.
So you will need to generate the TLS certificate for the Authentik instance even though you are accessing it from the local network?
If you want a straightforward way I think yes. Otherwise, and I'm taking guesses here, you should try to add the CA certificate to the root certificates on your operating system or browser
Also note that having a TLS certificate doesn't necessarily mean that you have to expose the services to the Internet. You can obtain a TLS certificate with a DNS01 challenge that does not require exposing anything publicly
I see, I think I can reverse proxy with https connection to my Authentik instance. Probably will resolve this issue. If I do that, do I have to change anything in the provider?
Perfect, you need to always have the URL you are accessing Authentik from in the redirect URIs in the provider. So in this case you should add your external domain in the redirect URIs
Hey, @EnricoBilla there will be some changes in the backend that I and the team will have to circle back and look at this PR since this is a big change to the project and it relates to authentication/security.
I will make a branch for this so the team can work on this feature to help you not have to update the PR to match the upstream branch.
You are welcome to let this open and keep working on that, I just don't want to discourage you by letting you keep having to update the branch without seeing it merge into upstream.
Thank you for your understanding.
Hi @alextran1502 Thanks for letting me know, actually I feel like all the features are implemented for this PR. I had to fix some bugs on the mobile app to skip the login page when the user is already authenticated.
Btw, did you manage to solve the issue we discussed in the previous messages?
Is there anything I can do to support this PR?
@PixelJonas I really don't know, I still want and would like to contribute to Immich with this PR. Tho I don't know what's the developer plan for the first release.
Backend and mobile app was completely working and I consider them finished. The web frontend was working, but since I had zero experience with Svelte, I really don't like the code there. If you are more experienced in this, it would be awesome if you could rewrite the implementation there.
In any case, the main branch was merged with this PR more than two months ago, I guess there's some work to do to merge from upstream now. On the other hand I'd prefer to merge only once when I know this PR is going to be merged soon.
I think @alextran1502 has to give a go / update here. I'd also offer support in resolving conflicts and rewriting parts of the code, if necessary.