-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
2,559 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[[source]] | ||
url = "https://pypi.org/simple" | ||
verify_ssl = true | ||
name = "pypi" | ||
|
||
[packages] | ||
uvicorn ="*" | ||
fastapi ="*" | ||
python-keycloak ="*" | ||
pydantic ="*" | ||
[dev-packages] | ||
|
||
[requires] | ||
python_version = "3.9" |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,26 @@ | ||
# keycloak-fastAPI-integration | ||
This repository illustrates how we can integrate keycloak with fastAPI for authetification | ||
|
||
This repository illustrates how we can integrate keycloak with fastAPI for authetification. | ||
This repo can be used as a template/code base for your app . | ||
|
||
|
||
## setup env | ||
|
||
I used `pipenv` for my env setup. | ||
|
||
1. install `pipenv` | ||
2. install dependencies using `pipenv install` | ||
3. run keycloak instance using: | ||
|
||
```bash | ||
docker run -p 8080:8080 -v ./keycloak/keycloak_data:/opt/keycloak/data/h2 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:22.0.3 start-dev | ||
``` | ||
|
||
4. now you can run your fastAPI app using `pipenv run python main.py` | ||
|
||
## Demo | ||
|
||
1. access to fastAPI swagger using http://127.0.0.1:8081/docs | ||
2. get token using authorize | ||
3. make your query for `/secure` | ||
4. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#/auth.py | ||
from fastapi.security import OAuth2AuthorizationCodeBearer | ||
from keycloak import KeycloakOpenID # pip require python-keycloak | ||
from config import settings | ||
from fastapi import Security, HTTPException, status,Depends | ||
from pydantic import Json | ||
from models import User | ||
|
||
# This is used for fastapi docs authentification | ||
oauth2_scheme = OAuth2AuthorizationCodeBearer( | ||
authorizationUrl=settings.authorization_url, # https://sso.example.com/auth/ | ||
tokenUrl=settings.token_url, # https://sso.example.com/auth/realms/example-realm/protocol/openid-connect/token | ||
) | ||
|
||
# This actually does the auth checks | ||
# client_secret_key is not mandatory if the client is public on keycloak | ||
keycloak_openid = KeycloakOpenID( | ||
server_url=settings.server_url, # https://sso.example.com/auth/ | ||
client_id=settings.client_id, # backend-client-id | ||
realm_name=settings.realm, # example-realm | ||
client_secret_key=settings.client_secret, # your backend client secret | ||
verify=True | ||
) | ||
|
||
async def get_idp_public_key(): | ||
return ( | ||
"-----BEGIN PUBLIC KEY-----\n" | ||
f"{keycloak_openid.public_key()}" | ||
"\n-----END PUBLIC KEY-----" | ||
) | ||
|
||
# Get the payload/token from keycloak | ||
async def get_payload(token: str = Security(oauth2_scheme)) -> dict: | ||
try: | ||
return keycloak_openid.decode_token( | ||
token, | ||
key= await get_idp_public_key(), | ||
options={ | ||
"verify_signature": True, | ||
"verify_aud": False, | ||
"exp": True | ||
} | ||
) | ||
except Exception as e: | ||
raise HTTPException( | ||
status_code=status.HTTP_401_UNAUTHORIZED, | ||
detail=str(e), # "Invalid authentication credentials", | ||
headers={"WWW-Authenticate": "Bearer"}, | ||
) | ||
|
||
# Get user infos from the payload | ||
async def get_user_info(payload: dict = Depends(get_payload)) -> User: | ||
try: | ||
return User( | ||
id=payload.get("sub"), | ||
username=payload.get("preferred_username"), | ||
email=payload.get("email"), | ||
first_name=payload.get("given_name"), | ||
last_name=payload.get("family_name"), | ||
realm_roles=payload.get("realm_access", {}).get("roles", []), | ||
client_roles=payload.get("realm_access", {}).get("roles", []) | ||
) | ||
except Exception as e: | ||
raise HTTPException( | ||
status_code=status.HTTP_400_BAD_REQUEST, | ||
detail=str(e), # "Invalid authentication credentials", | ||
headers={"WWW-Authenticate": "Bearer"}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#/config.py | ||
from models import authConfiguration | ||
|
||
|
||
settings = authConfiguration( | ||
server_url="http://localhost:8080/", | ||
realm="roc", | ||
client_id="rns:roc:portal", | ||
client_secret="", | ||
authorization_url="http://localhost:8080/realms/roc/protocol/openid-connect/auth", | ||
token_url="http://localhost:8080/realms/roc/protocol/openid-connect/token", | ||
) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# keycloak setup | ||
|
||
Run keycloak using either docker-compose or docker: | ||
|
||
```bash | ||
docker run -p 8080:8080 -v ./keycloak_data:/opt/keycloak/data/h2 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:22.0.3 start-dev | ||
``` | ||
|
||
You can also use your own instance of keycloak and just import the test realm using `roc.json` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
version: "3.7" | ||
|
||
volumes: | ||
keycloak: | ||
|
||
services: | ||
|
||
keycloak: | ||
image: quay.io/keycloak/keycloak:22.0.3 | ||
ports: | ||
- 8080:8080 | ||
environment: | ||
- KEYCLOAK_ADMIN=admin | ||
- KEYCLOAK_ADMIN_PASSWORD=admin | ||
volumes: | ||
- ./keycloak_data:/opt/keycloak/data/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#FileLock | ||
#Wed Sep 27 11:50:36 GMT 2023 | ||
server=172.17.0.2\:39219 | ||
hostName=7cdda6a8b4fd | ||
method=file | ||
id=18ad67a07d7593dd7b27224d024b24bc4f456b7b17a |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
2023-09-21 14:23:20 jdbc[3]: exception | ||
org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "MIGRATION_MODEL" not found (this database is empty); SQL statement: | ||
SELECT ID, VERSION FROM MIGRATION_MODEL ORDER BY UPDATE_TIME DESC [42104-220] | ||
2023-09-21 14:23:21 jdbc[3]: exception | ||
org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "DATABASECHANGELOG" not found (this database is empty); SQL statement: | ||
SELECT COUNT(*) FROM PUBLIC.DATABASECHANGELOG [42104-220] | ||
2023-09-21 14:23:21 jdbc[4]: exception | ||
org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "DATABASECHANGELOGLOCK" not found (this database is empty); SQL statement: | ||
SELECT COUNT(*) FROM PUBLIC.DATABASECHANGELOGLOCK [42104-220] | ||
2023-09-21 14:23:21 jdbc[3]: exception | ||
org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "DATABASECHANGELOG" not found; SQL statement: | ||
SELECT COUNT(*) FROM PUBLIC.DATABASECHANGELOG [42102-220] | ||
2023-09-21 14:29:40 jdbc[3]: exception | ||
org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-220] | ||
at org.h2.message.DbException.getJdbcSQLException(DbException.java:690) | ||
at org.h2.message.DbException.getJdbcSQLException(DbException.java:489) | ||
at org.h2.message.DbException.get(DbException.java:223) | ||
at org.h2.message.DbException.get(DbException.java:199) | ||
at org.h2.message.DbException.get(DbException.java:188) | ||
at org.h2.jdbc.JdbcConnection.checkClosed(JdbcConnection.java:1375) | ||
at org.h2.jdbcx.JdbcXAConnection$PooledJdbcConnection.checkClosed(JdbcXAConnection.java:473) | ||
at org.h2.jdbc.JdbcConnection.rollback(JdbcConnection.java:463) | ||
at org.h2.jdbcx.JdbcXAConnection$PooledJdbcConnection.close(JdbcXAConnection.java:453) | ||
at org.h2.jdbcx.JdbcXAConnection.close(JdbcXAConnection.java:76) | ||
at io.agroal.pool.ConnectionHandler.closeConnection(ConnectionHandler.java:185) | ||
at io.agroal.pool.ConnectionPool$DestroyConnectionTask.run(ConnectionPool.java:787) | ||
at io.agroal.pool.ConnectionPool.close(ConnectionPool.java:196) | ||
at io.agroal.pool.DataSource.close(DataSource.java:79) | ||
at io.quarkus.agroal.runtime.DataSources.stop(DataSources.java:454) | ||
at io.quarkus.agroal.runtime.DataSources_Bean.doDestroy(Unknown Source) | ||
at io.quarkus.agroal.runtime.DataSources_Bean.destroy(Unknown Source) | ||
at io.quarkus.agroal.runtime.DataSources_Bean.destroy(Unknown Source) | ||
at io.quarkus.arc.impl.AbstractInstanceHandle.destroyInternal(AbstractInstanceHandle.java:82) | ||
at io.quarkus.arc.impl.ContextInstanceHandleImpl.destroy(ContextInstanceHandleImpl.java:21) | ||
at io.quarkus.arc.impl.AbstractSharedContext.destroy(AbstractSharedContext.java:96) | ||
at io.quarkus.arc.impl.ArcContainerImpl.shutdown(ArcContainerImpl.java:468) | ||
at io.quarkus.arc.Arc.shutdown(Arc.java:66) | ||
at io.quarkus.arc.runtime.ArcRecorder$1.run(ArcRecorder.java:53) | ||
at io.quarkus.runtime.StartupContext.runAllInReverseOrder(StartupContext.java:84) | ||
at io.quarkus.runtime.StartupContext.close(StartupContext.java:73) | ||
at io.quarkus.runner.ApplicationImpl.doStop(Unknown Source) | ||
at io.quarkus.runtime.Application.stop(Application.java:208) | ||
at io.quarkus.runtime.Application.stop(Application.java:155) | ||
at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:227) | ||
at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) | ||
at org.keycloak.quarkus.runtime.KeycloakMain.start(KeycloakMain.java:98) | ||
at org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.run(AbstractStartCommand.java:37) | ||
at picocli.CommandLine.executeUserObject(CommandLine.java:2026) | ||
at picocli.CommandLine.access$1500(CommandLine.java:148) | ||
at picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2461) | ||
at picocli.CommandLine$RunLast.handle(CommandLine.java:2453) | ||
at picocli.CommandLine$RunLast.handle(CommandLine.java:2415) | ||
at picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2273) | ||
at picocli.CommandLine$RunLast.execute(CommandLine.java:2417) | ||
at picocli.CommandLine.execute(CommandLine.java:2170) | ||
at org.keycloak.quarkus.runtime.cli.Picocli.parseAndRun(Picocli.java:100) | ||
at org.keycloak.quarkus.runtime.KeycloakMain.main(KeycloakMain.java:88) | ||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) | ||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) | ||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) | ||
at java.base/java.lang.reflect.Method.invoke(Method.java:568) | ||
at io.quarkus.bootstrap.runner.QuarkusEntryPoint.doRun(QuarkusEntryPoint.java:61) | ||
at io.quarkus.bootstrap.runner.QuarkusEntryPoint.main(QuarkusEntryPoint.java:32) | ||
2023-09-21 14:29:40 jdbc[4]: exception | ||
org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-220] | ||
at org.h2.message.DbException.getJdbcSQLException(DbException.java:690) | ||
at org.h2.message.DbException.getJdbcSQLException(DbException.java:489) | ||
at org.h2.message.DbException.get(DbException.java:223) | ||
at org.h2.message.DbException.get(DbException.java:199) | ||
at org.h2.message.DbException.get(DbException.java:188) | ||
at org.h2.jdbc.JdbcConnection.checkClosed(JdbcConnection.java:1375) | ||
at org.h2.jdbcx.JdbcXAConnection$PooledJdbcConnection.checkClosed(JdbcXAConnection.java:473) | ||
at org.h2.jdbc.JdbcConnection.rollback(JdbcConnection.java:463) | ||
at org.h2.jdbcx.JdbcXAConnection$PooledJdbcConnection.close(JdbcXAConnection.java:453) | ||
at org.h2.jdbcx.JdbcXAConnection.close(JdbcXAConnection.java:76) | ||
at io.agroal.pool.ConnectionHandler.closeConnection(ConnectionHandler.java:185) | ||
at io.agroal.pool.ConnectionPool$DestroyConnectionTask.run(ConnectionPool.java:787) | ||
at io.agroal.pool.ConnectionPool.close(ConnectionPool.java:196) | ||
at io.agroal.pool.DataSource.close(DataSource.java:79) | ||
at io.quarkus.agroal.runtime.DataSources.stop(DataSources.java:454) | ||
at io.quarkus.agroal.runtime.DataSources_Bean.doDestroy(Unknown Source) | ||
at io.quarkus.agroal.runtime.DataSources_Bean.destroy(Unknown Source) | ||
at io.quarkus.agroal.runtime.DataSources_Bean.destroy(Unknown Source) | ||
at io.quarkus.arc.impl.AbstractInstanceHandle.destroyInternal(AbstractInstanceHandle.java:82) | ||
at io.quarkus.arc.impl.ContextInstanceHandleImpl.destroy(ContextInstanceHandleImpl.java:21) | ||
at io.quarkus.arc.impl.AbstractSharedContext.destroy(AbstractSharedContext.java:96) | ||
at io.quarkus.arc.impl.ArcContainerImpl.shutdown(ArcContainerImpl.java:468) | ||
at io.quarkus.arc.Arc.shutdown(Arc.java:66) | ||
at io.quarkus.arc.runtime.ArcRecorder$1.run(ArcRecorder.java:53) | ||
at io.quarkus.runtime.StartupContext.runAllInReverseOrder(StartupContext.java:84) | ||
at io.quarkus.runtime.StartupContext.close(StartupContext.java:73) | ||
at io.quarkus.runner.ApplicationImpl.doStop(Unknown Source) | ||
at io.quarkus.runtime.Application.stop(Application.java:208) | ||
at io.quarkus.runtime.Application.stop(Application.java:155) | ||
at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:227) | ||
at io.quarkus.runtime.Quarkus.run(Quarkus.java:71) | ||
at org.keycloak.quarkus.runtime.KeycloakMain.start(KeycloakMain.java:98) | ||
at org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.run(AbstractStartCommand.java:37) | ||
at picocli.CommandLine.executeUserObject(CommandLine.java:2026) | ||
at picocli.CommandLine.access$1500(CommandLine.java:148) | ||
at picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2461) | ||
at picocli.CommandLine$RunLast.handle(CommandLine.java:2453) | ||
at picocli.CommandLine$RunLast.handle(CommandLine.java:2415) | ||
at picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2273) | ||
at picocli.CommandLine$RunLast.execute(CommandLine.java:2417) | ||
at picocli.CommandLine.execute(CommandLine.java:2170) | ||
at org.keycloak.quarkus.runtime.cli.Picocli.parseAndRun(Picocli.java:100) | ||
at org.keycloak.quarkus.runtime.KeycloakMain.main(KeycloakMain.java:88) | ||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) | ||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) | ||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) | ||
at java.base/java.lang.reflect.Method.invoke(Method.java:568) | ||
at io.quarkus.bootstrap.runner.QuarkusEntryPoint.doRun(QuarkusEntryPoint.java:61) | ||
at io.quarkus.bootstrap.runner.QuarkusEntryPoint.main(QuarkusEntryPoint.java:32) |
Oops, something went wrong.