diff --git a/404.html b/404.html index 1df1b8e..cdf63b2 100644 --- a/404.html +++ b/404.html @@ -22,6 +22,9 @@ + + + @@ -53,7 +56,14 @@ -
An Omnivector initiative
+
Armasec is a security package that simplifies OIDC security for FastAPI apps.
Adding a security layer on top of your API can be difficult, especially when working with an OIDC diff --git a/minimal_example/index.html b/minimal_example/index.html index ecdeab8..c679af5 100644 --- a/minimal_example/index.html +++ b/minimal_example/index.html @@ -26,6 +26,9 @@ + + + @@ -57,7 +60,14 @@ -
Adding a security layer on top of your API can be difficult, especially when working with an OIDC platform. It's hard enough to get your OIDC provider configured correctly. Armasec aims to take the pain out of securing your APIs routes.
Armasec is an opinionated library that attemtps to use the most obvious and commonly used workflows when working with OIDC and making configuration as simple as possible.
When using the Armasec helper class, you only need two configuration settings to get going:
That's it! Once you have those settings dialed in, you can just worry about checking the permissions scopes of your endpoints
Armasec was originally developed as an internal tool to add security in tandem with Auth0. Since its inception, Armasec has been used in production with both Auth0 and Keycloak. It should work with other OIDC providers, assuming they are configured correctly, but the developers of Armasec make no guarantees for other platforms.
The armasec package can be installed like any other python package. It is available on PyPI
armasec
To install via poetry, simply run:
poetry
poetry add armasec\n
To install directly with pip, simply run:
pip
pip install armasec\n
The following is a minimal example of how to configure your API to enforce security on endpoints using tokens issued by Auth0.
First, create a file named example.py with the following contents:
example.py
import os\nfrom armasec import Armasec\nfrom fastapi import FastAPI, Depends\napp = FastAPI()\narmasec = Armasec(\ndomain=os.environ.get(\"ARMASEC_DOMAIN\"),\naudience=os.environ.get(\"ARMASEC_AUDIENCE\"),\n)\n@app.get(\"/stuff\", dependencies=[Depends(armasec.lockdown(\"read:stuff\"))])\nasync def check_access():\nreturn dict(message=\"Successfully authenticated!\")\n
In this example, you would have set two environment variables for your project settings:
ARMASEC_DOMAIN
my-auth.us.auth0.com
ARMASEC_AUDIENCE
https://my-api.my-domain.com
When you run your app, access to the /stuff endpoint would be restricted to authenticated users whose access tokens carried the permission scope \"read:stuff\".
/stuff
For a step-by-step walk-through of how to set up Auth0 for the minimal example, see the \"Getting Started with Auth0\" page.
The above code can be found in examples/basic.py.
This module defines the core Armasec class.
This is a factory class for TokenSecurity. It allows the machinery of armasec to be initialized correctly so that the factory method lockdown can initialize new instances of TokenSecurity to protect routes. It's not essential to use Armasec to secure routes, but it cuts down on the boilerplate necessary to do so.
lockdown
__init__(domain_configs: Optional[List[DomainConfig]] = None, debug_logger: Optional[Callable[[str], None]] = noop, debug_exceptions: bool = False, **kargs: bool)\n
Stores initialization values for the TokenSecurity. All are passed through.
Parameters:
domain_configs
Optional[List[DomainConfig]]
List of domain configuration to authenticate the tokens against.
None
debug_logger
Optional[Callable[[str], None]]
A callable, that if provided, will allow debug logging. Should be passed as a logger method like logger.debug
logger.debug
noop
debug_exceptions
bool
If True, raise original exceptions. Should only be used in a testing or debugging context.
False
kargs
Arguments compatible to instantiate the DomainConfig model.
{}
cached
lockdown(*scopes: str, permission_mode: PermissionMode = PermissionMode.ALL) -> TokenSecurity\n
Initialize an instance of TokenSecurity to lockdown a route. Uses memoization to minimize the number of TokenSecurity instances initialized. Applies supplied permission_mode when checking token permssions against TokenSecurity scopes.
scopes
str
A list of scopes needed to access the endpoint.
()
permissions_mode
If \"ALL\", all scopes listed are required for access. If \"SOME\", only one of the scopes listed are required for access.
lockdown_all(*scopes: str) -> TokenSecurity\n
Initialize an instance of TokenSecurity to lockdown a route. Uses memoization to minimize the number of TokenSecurity instances initialized. Requires all the scopes in the TokenSecurity instance to be included in the token permissions. This is just a wrapper around lockdown() with default permission_mode and is only included for symmetry.
lockdown()
A list of the scopes needed to access the endpoint. All are required.
lockdown_some(*scopes: str) -> TokenSecurity\n
Initialize an instance of TokenSecurity to lockdown a route. Uses memoization to minimize the number of TokenSecurity instances initialized. Requires at least one permission in the token to match a scope attached to the TokenSecurity instance.
A list of the scopes needed to access the endpoint. Only one is required.
Bases: Buzz
Buzz
A custom exception class used for checking conditions and handling other exceptions.
Attributes:
status_code
int
The HTTP status code indicated by the error. Set to 400.
Bases: ArmasecError
ArmasecError
Indicates a failure to authenticate and decode jwt.
The HTTP status code indicated by the error. Set to 401.
Indicates that the provided claims don't match the claims required for a protected endpoint.
The HTTP status code indicated by the error. Set to 403.
Indicates that the configured payload_claim_mapping did not match a path in the token.
The HTTP status code indicated by the error. Set to 500.
This module provides the OpenidConfigLoader which is used to load openid-configuration data from an OIDC provider.
property
config: OpenidConfig\n
Retrive the openid config from an OIDC provider. Lazy loads the config so that API calls are deferred until the coniguration is needed.
jwks: JWKs\n
Retrives JWKs public keys from an OIDC provider. Lazy loads the jwks so that API calls are deferred until the jwks are needed.
__init__(domain: str, use_https: bool = True, debug_logger: Optional[Callable[..., None]] = None)\n
Initializes a base TokenManager.
secret
The secret key needed to decode a token
domain
The domain of the OIDC provider. This is to construct the openid-configuration url
use_https
If falsey, use http instead of https (the default).
http
https
True
Optional[Callable[..., None]]
staticmethod
build_openid_config_url(domain: str, use_https: bool = True)\n
Builds a url for an openid configuration given a domain.
The domain of the OIDC provider for which to build a URL
Use https for the URL by default. If falsey, use http instead.
This module provides a pytest plugin for testing.
build_mock_openid_server(domain, openid_config, jwk, jwks_uri) -> Callable[[str, OpenidConfig, JWK, str], _GeneratorContextManager]\n
Provide a fixture that returns a context manager that mocks opend-config routes.
The domain of the openid server to mock.
openid_config
The config to return from the mocked config route.
jwk
The jwk to return from the mocked jwk route.
jwks_uri
The URL of the jwks route to mock.
Returns:
Callable[[str, OpenidConfig, JWK, str], _GeneratorContextManager]
A context manager that, while active, mocks the openid routes needed by Armasec.
build_rs256_token(rs256_private_key, rs256_iss, rs256_sub, rs256_kid)\n
Provide a fixture that returns a helper method that can build a JWT.
The JWT is signed with the private key provided by the rs256_private_key.
rs256_private_key
An implicit fixture parameter.
rs256_iss
rs256_sub
mock_openid_server(rs256_domain, rs256_openid_config, rs256_jwk, rs256_jwks_uri)\n
Provide a fixture that mocks an openid server using the extension fixtures.
rs256_domain
rs256_openid_config
rs256_jwk
rs256_jwks_uri
rs256_domain()\n
Provide a fixture that returns a domain for use in other fixtures.
The value here doesn't really have anything to do with an actual domain name.
rs256_domain_config(rs256_domain)\n
Provide a fixture that returns the DomainConfig model for the default rs256 domain.
rs256_iss(rs256_domain)\n
Provide a fixture that returns an issuer claim for use in other fixtures.
rs256_jwk(rs256_kid)\n
Provide a fixture that returns a JWK for use in other fixtures.
rs256_kid
rs256_jwks_uri(rs256_domain)\n
Provide a fixture that returns a jwks uri for use in other fixtures.
rs256_kid()\n
Provide a fixture that returns a KID header value for use in other fixtures.
rs256_openid_config(rs256_iss, rs256_jwks_uri)\n
Provide a fixture that returns an openid configuration for use in other fixtures.
rs256_private_key()\n
Provide a fixture that returns a pre-generated private key for RS256 hashing in other fixtures.
rs256_public_key()\n
Provide a fixture that returns a pre-generated public key for RS256 hashing in other fixtures.
rs256_sub()\n
Provide a fixture that returns a sum claim for use in other fixtures.
This module provides an abstract base class for algorithmic token decoders
Decoder class used to decode tokens given an algorithm and jwks.
__init__(jwks: JWKs, algorithm: str = 'RS256', debug_logger: Callable[..., None] | None = None, decode_options_override: dict | None = None, payload_claim_mapping: dict | None = None)\n
Initializes a TokenDecoder.
algorithm
The algorithm to use for decoding. Defaults to RS256.
'RS256'
jwks
JWKs
JSON web keys object holding the public keys for decoding.
The openid_configuration needed for claims such as 'issuer'.
Callable[..., None] | None
decode_options_override
dict | None
Options that can override the default behavior of the jwt decode method. For example, one can ignore token expiration by setting this to { \"verify_exp\": False }
{ \"verify_exp\": False }
payload_claim_mapping
Optional mappings that are applied to map claims to top-level attribute of TokenPayload using a dict format of:
```\n {\n \"top_level+attribute\": \"decoded.token.JMESPath\"\n }\n ```\n The values _must_ be a valid JMESPath.\n\n Consider this example:\n\n ```\n {\n \"permissions\": \"resource_access.default.roles\"\n }\n ```\n\n The above example would result in a TokenPayload like:\n\n ```\n TokenPayload(permissions=token[\"resource_access\"][\"default\"][\"roles\"])\n ```\n Raises a 500 if the path does not match\n
decode(token: str, **claims: str) -> TokenPayload\n
Decode a JWT into a TokenPayload while checking signatures and claims.
token
The token to decode.
claims
Additional claims to verify in the token.
get_decode_key(token: str) -> dict\n
Search for a public keys within the JWKs that matches the incoming token.
Compares the token's unverified header against available JWKs. Uses the matching JWK for the decode key. Raise AuthenticationError if matching public key cannot be found.
The token to match against available JWKs.
This module defines a TokenManager that can be used to extract token payloads from request headers.
Handle auth via a TokenDecoder and manage extraction from request headers and serialization into TokenPayload instances.
__init__(openid_config: OpenidConfig, token_decoder: TokenDecoder, audience: Optional[str] = None, debug_logger: Optional[Callable[..., None]] = None, decode_options_override: Optional[dict] = None)\n
Initialize a base TokenManager.
OpenidConfig
token_decoder
TokenDecoder
The decoder used to verify jwts
audience
Optional[str]
An optional audience to check in decoded tokens.
Optional[dict]
extract_token_payload(headers: Union[Headers, dict]) -> TokenPayload\n
Retrieve a token from a request header and decode it into a TokenPayload.
headers
Union[Headers, dict]
The headers from which to retrieve a JWT.
unpack_token_from_header(headers: Union[Headers, dict]) -> str\n
Unpack a JWT from a request header.
The headers from which to unpack a JWT.
This module defines a pydantic schema for the payload of a jwt.
Bases: BaseModel
BaseModel
A convenience class that can be used to access parts of a decoded jwt.
sub
The \"sub\" claim from a JWT.
permissions
List[str]
The permissions claims extracted from a JWT.
expire
datetime
The \"exp\" claim extracted from a JWT.
client_id
The \"azp\" claim extracted from a JWT.
to_dict()\n
Convert a TokenPayload to the equivalent dictionary returned by jwt.decode().
jwt.decode()
This module defines a TokenSecurity injectable that can be used enforce access on FastAPI routes.
Model class to represent a TokenManager instance and its domain configuration for easier mapping
manager
TokenManager
The TokenManager instance to use for decoding tokens.
domain_config
DomainConfig
The DomainConfig for the openid server.
Bases: AutoNameEnum
AutoNameEnum
Endpoint permissions.
ALL
Require all listed permissions.
SOME
Require at least one of the listed permissions.
Bases: APIKeyBase
APIKeyBase
An injectable Security class that returns a TokenPayload when used with Depends().
Optional[TokenManager]
The TokenManager to use for token validation and extraction.
async
__call__(request: Request) -> TokenPayload\n
This method is called by FastAPI's dependency injection system when a TokenSecurity instance is injected to a route endpoint via the Depends() method. Lazily loads the OIDC config, the TokenDecoder, and the TokenManager if they are not already initialized.
request
Request
The FastAPI request to check for secure access.
__init__(domain_configs: List[DomainConfig], scopes: Optional[Iterable[str]] = None, permission_mode: PermissionMode = PermissionMode.ALL, debug_logger: Optional[Callable[..., None]] = None, debug_exceptions: bool = False)\n
Initializes the TokenSecurity instance.
List[DomainConfig]
Optional[Iterable[str]]
Optional permissions scopes that should be checked
permission_mode
PermissionMode
The PermissionMode to apply in the protected route.
Provides some utility functions.
log_error(logger: Callable[..., None], dep: DoExceptParams)\n
Logs an en error with the supplied message, a string representation of the error, and its traceback. If the logger supplied is noop, do nothing. Pass as a partial when using the Buzz handle_errors context manager::
handle_errors
with Buzz.handle_errors(\"Boom!\", do_except=partial(log_error, debug_logger)):\n do_some_risky_stuff()\n
noop(*args, **kwargs)\n
This is a no-op function that...does nothing.
This module provides a pydantic schema describing Armasec's configuration parameters.
This model provides a specification for the input domains to authenticate against. It expects the domain indeed and the audience to refer to.
The OIDC domain from which resources are loaded.
Optional designation of the token audience.
The Algorithm to use for decoding. Defaults to RS256.
If true, use https for URLs. Otherwise use http
match_keys
Dict[str, Union[str, List[Any], Dict[Any, Any], Set[Any], bool, int, float]]
Dictionary of k/v pairs to match in the token when decoding it.
This module provides pydantic schemas for JSON Web Keys.
This Model provides a specification for the objects retrieved from JWK endpoints in OIDC providers. It also assists with validation and item access.
alg
The algorithm to use for hash validation.
e
The exponent parameter to use in RS256 hashing
kid
The \"kid\" claim to uniquely identify the key.
kty
The \"kty\" claim to identify the type of the key.
n
The modulus parameter to use in RS256 hashing.
use
The claim that identifies the intended use of the public key.
x5c
Optional[List[str]]
The X.509 certificate chain parameter
The X.509 certificate SHA-1 thumbprint parameter
This Model provides a specification for the container object retrieved from JWK endpoints in OIDC providers. It also assists with validation and item access.
keys
List[JWK]
The list of JWKs contained within.
This module provides a pydantic schema describing openid-configuration data.
Provides a specification for the objects retrieved from openid_configuration endpoint of the OIDC providers. Only includes needed fields for supported Manager instances. Assists with validation and item access.
issuer
AnyHttpUrl
The URL of the issuer of the tokens.
The URI where JWKs can be foun don the OpenID server.
This step-by-step walk-through will show you how to get an Auth0 account set up for use with the minimal example.
You will need:
uvicorn
Navigate to Auth0's homepage, and click Sign Up.
Sign Up
Create a new account (if you don't already have one). It doesn't really matter how you choose to sign up, but I used my google account.
Create a personal account
For this tutorial, just select a \"Personal\" account. You can come back and repeat the process for your company later if you need to.
Select I need advanced settings so that you can choose your tenant name, and click Next to move on to tenant setup.
I need advanced settings
Next
Select tenant domain
Select your tenant domain. For this tutorial, I will be using \"armasec-tutorial\". Choose your region and click Create Account. The full domain that I will use later is \"armasec-tutorial.us.auth0.com\".
Create Account
This will set up the Auth0 api endpoints that Auth0 will use to authenticate your users.
Navigate to Applications > APIs
Click Applications -> APIs
Applications
APIs
Create an API
Click Create API
Create API
Fill out API form
Fill out the form. The Name you choose doesn't matter, but for the tutorial, I used \"Armasec Tutorial\". Choose a name for the identifier that makes sense, as this will be the \"Audience\" for your tutorial app. I chose \"https://armasec-tutorial.com\" for this tutorial.
Leave the \"Signing Algorithm\" as \"RS256\". Armasec also supports HS256, but it is not preferred.
Click Create
Create
Enable RBAC Settings
Go to the Settings tab and scroll down to the \"RBAC Settings\"
Activate Enable RBAC and Add Permissions in the Access Token\".
Enable RBAC
Add Permissions
This will include permissions scopes to your users' access tokens.
Click Save
Save
Next, we will add the permission for the minimal example.
Add a permission
Click on the Permissions tab and add the \"read:stuff\" permission.
Permissions
Auth0 provides a \"Machine-to-Machine\" test app for verifying that log in will work. We will grant our \"read:stuff\" permission to tokens provided from this app.
Navigate to Applications
Navigate to Applications -> Applications
Select tutorial test app
Click on Armasec Tutorial (Test Application)
Armasec Tutorial (Test Application)
Add permission to test app
Go to the APIs tab and click the drop-down. Select the \"read-stuff\" box in the \"Permissions\" box. Click Update.
Update
Copy the example app to a local source file called \"example.py\".
Start it up with uvicorn:
python -m uvicorn --host 0.0.0.0 example:app\n
Next, we will try to use a test token to call our minimal example's endpoint.
Navigate back to Applications -> APIs, select the Armasec Tutorial, and go to the Test tab.
Armasec Tutorial
Copy test access token
Find the \"Response\" box. This contains a token that has been issued by your test application. Click the Copy Token button.
Copy Token
Now, open a browser to \"localhost:8000/docs\"
Swagger page for example
This will show you the auto-generated swagger docs for the example API app. Click on the Authorize button to add the token you copied to the clipboard in the header of requests to the API.
Authorize
Add access token in Swagger
First, type \"Bearer \" followed by pasting the token from the clipboard into the form and click the Authorize button in the dialog, click Close to dismiss the dialog. Now, all subsequent calls to the API will include a header that looks like:
Close
{\n\"Authorization\": \"Bearer eyJhbGciOi...\"\n}\n
Now, expand the \"GET\" REST operation on the /stuff endpoint and click Try it out.
Try it out
Finally, click Execute to issue the request to the API.
Execute
You should see a response that includes a 200 status code and a response body that includes:
{\n\"message\": \"Successfully authenticated\"\n}\n
Congratulations! You are now using Armasec and Auth0 to authorize requests to your API.
Now, there are a few things you can do to check out how things work. Try the following things:
curl
httpx
If the above tutorial didn't work as expected, a step needs better clarification, or you have some questions about it, please create an issue on Armasec's GitHub's issues.
This step-by-step walk-through will show you how to get your Keycloak server set up to work with the minimal example. For this tutorial, I am running Keycloak locally with Docker.
The first thing you will need to do is to install some neeeded python dependencies. In order to run the example API, you will need the the uvicorn package installed. You will also be using the Armasec CLI to verify that you can log in. For this, you will need the armasec package with the cli extra. For this tutorial, it will be best to work in a new virtual environment. To bootstrap it, run the following commands:
cli
python -m venv env\nsource env/bin/activate\npip install uvicorn armasec[cli]\n
When the commands finish running, your environment will be ready to complete this guide.
If you want to try the example locally without an existing Keycloak server, you will need to start a new one. Docker is the easiest way to get Keycloak up and running. To start the Keycloak server in Docker, run the following command:
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=\"admin\" -e KEYCLOAK_ADMIN_PASSWORD=\"admin\" keycloak/keycloak:22.0.5 start-dev\n
This will start a new Keycloak server with an admin UI available at localhost:8080. A new admin user will automatically be created with the password \"admin\".
admin
Open a browser and load the address localhost:8080. Then, click on the \"Administration Console\" to log in.
localhost:8080
Log-in to admin console
The sign-in credentials for the admin account are:
After you log in, the UI will automatically load the \"Master\" realm.
First, you should change the lifespan for tokens. By default they expire after 1 minute, but that's not so friendly for a tutorial, so you should lengthen the lifespan to 15 minutes.
Click on the \"Realm settings\" section in the side-nav, and then go to the \"Tokens\" tab. Find the \"Access Token Lifespan\" setting, and change it to 15 minutes.
Realm settings
Click \"Save\" at the bottom after you change the value. All of the rest of the settings can be left as they are.
You will need a client that is configured for the tutorial. To do this, navigate to the \"Clients\" tab in the nav bar and click the \"Create client\" button in the \"Clients list\" tab:
Create the client
A creation wizard will load starting with the \"General Settings\" tab. For this tutorial, use the name \"armasec_tutorial\". Click the \"Next\" button to go on to the next page.
Name the client
In the \"Capability config\" page, select the \"OAuth 2.0 Device Authorization Grant\" setting. This will be needed to log in via the Armasec CLI later. Click the \"Next\" button to go on to the next page.
Device auth
Finally, in the \"Login settings\" page, put \"*\" in the \"Valid redirect URIs\" setting. Click the \"Save\" button to finish creating the new client.
Redirect URIs
Once the new client is created, you will be redirected to the \"Client details\" page.
This tutorial demonstrates how to set up role-based access on an API endpoint. So, the next step is to create a role that the API will require users to have in order to access an endpoint.
Click on the \"Roles\" tab of the \"Client Details\" page, and then click the \"Create role\" button.
Create Role
The name is not important as long as it matches on your API endpoint. For this tutorial, name the role \"read:stuff\" and then click the \"Save\" button.
Name Role
The final step for our new client is to set up an \"Audience\" mapper to set the audience claim in the token that our example app will check for.
Navigate back to the \"Client details\" for our \"armasec_tutorial\" client and open the \"Client scopes\" tab. To add a new mapper for our client, click the link for \"armasec_tutorial-dedicated\" scope.
Client Scopes
The \"Mappers\" tab will load by default. Click the \"Configure a new mapper\" button.
Configure Mapper
Select the \"Audience\" mapper from the list that is shown.
Select Audience
The \"Add mapper\" form will be loaded next. In this form, name the mapper \"Audience\" and set the \"Included Custom Audience\" field to \"http://keycloak.local\". Make sure \"Add to ID token\" is selected. Finally, click \"Save\" to finish adding the new mapper.
Mapper Settings
The last step in configuring keycloak is setting up a user that will be accessing the protected endpoint.
Navigate to the \"Users\" section using the side-nav and click the \"Add user\" button to get started.
Add User
In the \"Create user\" form that is opened next, name your user \"local-user\" and provide a fake email address like \"local-user@keycloak.local\". Click \"Create\" to create the new user.
Create User
Next, we need to assign the user a password that they will use to log in. Open the \"Credentials\" tab in the \"User details\" page for your new user. Click the \"Set password\" button.
Set Password
In the form that opens, use \"local\" for the password (and confirmation). Make sure to turn off the \"Temporary\" flag so that the user will not have to change their password in this tutoria. Then, click the \"Save\" button to finish setting up the user credentials.
Save Password
Next, we need to add the \"read:stuff\" role that we created before to your new user. Open the \"Role mapping\" tab in the \"User details\" page for your new user. Click the \"Assign role\" button.
Assign Role
In the \"Assign roles to local-user\" dialog that opens, click the drop down that shows \"Filter by realm roles\" and switch the setting to \"Filter by clients\". Find the \"read:stuff\" role that was assigned to the \"armasec_tutorial\" client, select it, and click the \"Assign\" button to assign this role to your new user.
Select Role
Now your client should be all set up and ready to go! Log out of the admin portal before you go on so that you are ready to log in with your new user later on in the tutorial.
from armasec import Armasec\nfrom fastapi import FastAPI, Depends\napp = FastAPI()\narmasec = Armasec(\ndomain=\"localhost:8080/realms/master\",\naudience=\"http://keycloak.local\",\nuse_https=False,\npayload_claim_mapping=dict(permissions=\"resource_access.armasec_tutorial.roles\"),\ndebug_logger=print,\ndebug_exceptions=True,\n)\n@app.get(\"/stuff\", dependencies=[Depends(armasec.lockdown(\"read:stuff\"))])\nasync def check_access():\nreturn dict(message=\"Successfully authenticated!\")\n
Note in this example that the use_https flag must be set to false to allow a local server using unsecured HTTP.
Also not that we need to add a payload_claim_mapping because Keycloak does not provide a permissions claim at the top level. This mapping copies the roles found at resource_access.armasec_tutorial.roles to a top-level attribute of the token payload called permissions.
resource_access.armasec_tutorial.roles
Copy the example.py app to a local source file called \"example.py\".
python -m uvicorn --host 0.0.0.0 --port 5000 example:app\n
Once it is up and running, hit <ctrl-z> and type the command bg to put the uvicorn process into the background. You should make sure to kill the process when you complete the tutorial.
<ctrl-z>
bg
Next, you will log in using the Armasec CLI to get an API token to access your example API. Before you log in, however, you will need to configure the Armasec CLI to connect to your Keycloak server. Type the following command to configure the CLI:
armasec set-config --domain=localhost:8080/realms/master --audience=http://keycloak.local --no-use-https --client-id=armasec_tutorial\n
Now you should be ready to log in. In your terminal, type the following command to start the process:
armasec login\n
The CLI will show a box prompting you to complete the login process in a browser:
\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Waiting for login \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 \u2502\n\u2502 To complete login, please open the following link in a browser: \u2502\n\u2502 \u2502\n\u2502 http://localhost:8080/realms/master/device?user_code=TQOL-RIYP \u2502\n\u2502 \u2502\n\u2502 Waiting up to 6.0 minutes for you to complete the process... \u2502\n\u2502 \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\n\nWaiting for web login... \u2578\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 2% 0:04:56\n
Open the provided link in the browser and log in using the \"local-user\" that you created.
Log in
You will then be prompted with a question asking if you want to grant access. Click \"Yes\" to complete the log in process. The browser will show a message confirming that you are now logged in. You may close that browser tab.
Notice in the terminal, a message is now printed showing that your user was logged in:
\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Logged in! \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 \u2502\n\u2502 User was logged in with email 'local-user@keycloak.local' \u2502\n\u2502 \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\n
Next, you will use the Armasec CLI to fetch the token for the logged in user. To do this, you will use the show-token subcommand. In a terminal, type (the prefix flag includes the \"Bearer\" type indicator for the token):
show-token
armasec show-token --prefix\n
The Armasec CLI will print out a box showing the auth token for your user:
\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Access Token \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 \u2502\n\u2502 Bearer \u2502\n\u2502 eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3dzlNZ0FSNExXbXNuTE9vYTJKTkE4WXZkVUFZX25jZnNvcTU0c3d \u2502\n\u2502 kbERzIn0.eyJleHAiOjE3MDA1OTE2OTAsImlhdCI6MTcwMDU5MTYzMCwiYXV0aF90aW1lIjoxNzAwNTkxNjI4LCJqdGkiOiI0ZTNmN2 \u2502\n\u2502 U4Ni00ZTJhLTQwZDAtOWQ3ZS0zZTRmYjNkMzk1YWYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL21hc3RlciIsI \u2502\n\u2502 mF1ZCI6WyJodHRwOi8va2V5Y2xvYWsubG9jYWwiLCJhY2NvdW50Il0sInN1YiI6IjMxMzY1ZWYyLTZmYjYtNGVhOS04MmE2LWUwMTli \u2502\n\u2502 YjYwNmFkOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFybWFzZWNfdHV0b3JpYWwiLCJzZXNzaW9uX3N0YXRlIjoiYTc2ZWEzYTEtZGE \u2502\n\u2502 3MC00Mzg2LWFiZTMtZjFjZjYzZDI5ZjZhIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLW \u2502\n\u2502 1hc3RlciIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhcm1hc2VjX3R1d \u2502\n\u2502 G9yaWFsIjp7InJvbGVzIjpbInJlYWQ6c3R1ZmYiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2Ut \u2502\n\u2502 YWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6ImE3NmVhM2ExLWRhNzA \u2502\n\u2502 tNDM4Ni1hYmUzLWYxY2Y2M2QyOWY2YSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoibG9jYWwtdX \u2502\n\u2502 NlciIsImVtYWlsIjoibG9jYWwtdXNlckBrZXljbG9hay5sb2NhbCJ9.ntEA67CNS2ZvPIMac3X-1wKBTiKFS5i5aYo32M7ytVIrnh_X \u2502\n\u2502 j_YHLz17WmP3PBKyJtZKgIN8zq_nOF4XeRBMMHSg8ec9ySRNNRia0AkB0AKB-yPa4Q2qGwAFFipWhkP_iQapHj3XWPNDSVRPy8ZvRzb \u2502\n\u2502 LDjcgxhvSQE2Yzm68dtiVrcxA-FpImtJRNwARgeXFcvsYjrWfaACLVrvABgi0PiBiqPoFE4-zHEwhVZ3-DfmvXGRj4NxVsOzTyVkzi0 \u2502\n\u2502 pfMgHtOzI3MHb_hQ2xAtNBp-Ra5yYXHV3hteb_RPfjVTYADl6fq5Rggi3ydPsJVs0I7GAwzh85P8wRs127dtYv1w \u2502\n\u2502 \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 The output was copied to your clipboard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\n
If your terminal supports it, the token will be automatically copied into your clipboard. However, if you need to manually copy it, you can run the above command again with the --plain flag to print the token without formatting:
--plain
Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3dzlNZ0FSNExXbXNuTE9vYTJKTkE4WXZkVUFZX25jZnNvcTU0c3dkbERzIn0.eyJleHAiOjE3MDA1OTE2OTAsImlhdCI6MTcwMDU5MTYzMCwiYXV0aF90aW1lIjoxNzAwNTkxNjI4LCJqdGkiOiI0ZTNmN2U4Ni00ZTJhLTQwZDAtOWQ3ZS0zZTRmYjNkMzk1YWYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL21hc3RlciIsImF1ZCI6WyJodHRwOi8va2V5Y2xvYWsubG9jYWwiLCJhY2NvdW50Il0sInN1YiI6IjMxMzY1ZWYyLTZmYjYtNGVhOS04MmE2LWUwMTliYjYwNmFkOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFybWFzZWNfdHV0b3JpYWwiLCJzZXNzaW9uX3N0YXRlIjoiYTc2ZWEzYTEtZGE3MC00Mzg2LWFiZTMtZjFjZjYzZDI5ZjZhIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLW1hc3RlciIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhcm1hc2VjX3R1dG9yaWFsIjp7InJvbGVzIjpbInJlYWQ6c3R1ZmYiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6ImE3NmVhM2ExLWRhNzAtNDM4Ni1hYmUzLWYxY2Y2M2QyOWY2YSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoibG9jYWwtdXNlciIsImVtYWlsIjoibG9jYWwtdXNlckBrZXljbG9hay5sb2NhbCJ9.ntEA67CNS2ZvPIMac3X-1wKBTiKFS5i5aYo32M7ytVIrnh_Xj_YHLz17WmP3PBKyJtZKgIN8zq_nOF4XeRBMMHSg8ec9ySRNNRia0AkB0AKB-yPa4Q2qGwAFFipWhkP_iQapHj3XWPNDSVRPy8ZvRzbLDjcgxhvSQE2Yzm68dtiVrcxA-FpImtJRNwARgeXFcvsYjrWfaACLVrvABgi0PiBiqPoFE4-zHEwhVZ3-DfmvXGRj4NxVsOzTyVkzi0pfMgHtOzI3MHb_hQ2xAtNBp-Ra5yYXHV3hteb_RPfjVTYADl6fq5Rggi3ydPsJVs0I7GAwzh85P8wRs127dtYv1w\n
Keep the token close at hand; you will be using it shortly to access your API endpoint.
Open a browser to localhost:8000/docs
Swagger
First, paste the token (including the \"Bearer\" prefix) from the clipboard into the form and click the Authorize button in the dialog, click Close to dismiss the dialog. Now, all subsequent calls to the API will include a header that looks like: