yourkarma/jwt

repository·master·Indexed 18 days ago

https://github.com/yourkarma/jwt

An Objective-C implementation of the JSON Web Token (JWT) standard. It provides tools for encoding, decoding, signing, and verifying tokens using algorithms such as HMAC, RSA, and EC. The library features a fluent API via JWTBuilder, JWTEncodingBuilder, and JWTDecodingBuilder, and supports custom claims implementation and algorithm chaining via JWTAlgorithmDataHolderChain.

Tokens
5.9K
Snippets
17
Records
19
Agent score
13%

What's inside yourkarma-jwt

  1. Validate claims during decoding

    master

    You can verify that a decoded JWT matches a trusted set of claims by passing a JWTClaimsSet to the .claimsSet() method during decoding. After calling .decode, check the jwtError property on the builder to see if validation failed.

    // Trusted Claims Set
    JWTClaimsSet *trustedClaimsSet = [[JWTClaimsSet alloc] init];
    trustedClaimsSet.issuer = @"Facebook";
    // ... set other properties
    
    NSString *message = @"encodedJwt";
    NSString *secret = @"secret";
    NSString *algorithmName = @"chosenAlgorithm";
    
    JWTBuilder *builder = [JWTBuilder decodeMessage:message].secret(secret).algorithmName(algorithmName).claimsSet(trustedClaimsSet);
    NSDictionary *payload = builder.decode;
    
    if (builder.jwtError == nil) {
        // Success: claims matched
    } else {
        // Error: claims did not match or decoding failed
    }
  2. How JWTBuilder works

    master
    The JWTBuilder follows a fluent interface pattern. You initiate a builder using a static method like encodePayload:, encodeClaimsSet:, or decodeMessage:. You then chain configuration methods (like .secret(), .algorithm(), or .whitelist()) to set the necessary parameters. Finally, you call the terminal method .encode (for encoding) or .decode (for decoding) to perform the operation. Errors are not thrown as exceptions but are captured in the jwtError property of the builder instance.
  3. How Algorithms and Data Holders work with Chains

    master

    In version 3.0, the library introduces JWTAlgorithmDataHolderProtocol and JWTAlgorithmDataHolderChain. This allows you to attempt decoding a token using multiple possible algorithms or secrets in sequence.

    Using a Chain to try multiple algorithms

    You can create a chain of holders. If the first algorithm fails, the decoder can move to the next.

    // Create holders
    id <JWTAlgorithmDataHolderProtocol> firstHolder = [JWTAlgorithmHSFamilyDataHolder new].algorithmName(JWTAlgorithmNameHS384).secret(@"first");
    id <JWTAlgorithmDataHolderProtocol> errorHolder = [JWTAlgorithmNoneDataHolder new];
    
    // Chain them together
    JWTAlgorithmDataHolderChain *chain = [[JWTAlgorithmDataHolderChain alloc] initWithHolders:@[firstHolder, errorHolder]];
    
    // Use with a builder
    [JWTDecodingBuilder decodeMessage:token].chain(chain);

    Using a Chain to try multiple secrets

    If you have one algorithm but multiple possible secrets, you can expand a chain with multiple secret data objects.

    // Create a chain with one holder
    JWTAlgorithmDataHolderChain *chain = [JWTAlgorithmDataHolderChain chainWithHolder:firstHolder];
    
    // Populate the chain with multiple secrets (as NSData)
    JWTAlgorithmDataHolderChain *expandedChain = [chain chainByPopulatingAlgorithm:firstHolder.currentAlgorithm withManySecretData:manySecretsData];
    JWTAlgorithmDataHolderChain *chain = [[JWTAlgorithmDataHolderChain alloc] initWithHolders:@[firstHolder, errorHolder]];
    [JWTDecodingBuilder decodeMessage:token].chain(chain);
  4. Register and Use Custom Claims with ClaimsSetCoordinator

    master

    Once your Claim, Serializer, and Verifier are defined, you must register them with a JWTClaimsSetCoordinator to enable automatic handling during encoding and decoding.

    Registration Workflow:

    1. Create an instance of JWTClaimsSetCoordinatorBase.
    2. Use registerClaim:serializer:verifier:forClaimName: to link your custom components to a specific claim name.
    3. Use the coordinator to configure a JWTClaimsSetDSL (Domain Specific Language) to populate your claims.
    4. Use the coordinator's claimsSetSerializer to convert between the internal claimsSetStorage and a standard dictionary.
    // Setup
    __auto_type claim = JWTClaimVariations.intersectionOfIntervals;
    __auto_type claimSerializer = JWTClaimSerializerVariations.interval;
    __auto_type claimVerifier = JWTClaimVerifierVariations.intersection;
    
    id<JWTClaimsSetCoordinatorProtocol> claimsSetCoordinator = [JWTClaimsSetCoordinatorBase new];
    [claimsSetCoordinator registerClaim:claim 
                            serializer:claimSerializer 
                             verifier:claimVerifier 
                          forClaimName:JWTClaimsNames.intersectionOfIntervals];
    
    // Populate data via DSL
    [claimsSetCoordinator.configureClaimsSet:^(JWTClaimsSetDSLBase *claimsSetDSL) {
        claimsSetDSL.intersection = @[@(2), @(5)];
    }];
  5. Implement Custom Claims in Objective-C

    master

    To add custom logic for specific JWT claims (such as specialized data types or validation rules), you must implement three core components: a Claim, a Serializer, and a Verifier. This allows you to map complex local objects (like an array of numbers) to specific JSON representations (like a comma-separated string) and validate them against trusted values during decoding.

    Implementation Steps:

    1. Define a Claim: Subclass JWTClaimBase and define its unique name using JWTClaimsNames.
    2. Define a Serializer: Subclass JWTClaimSerializerBase. Implement deserializedClaimValue:forName: to convert JSON values into your local object type, and serializedClaimValue: to convert your local object back into a JSON-compatible format.
    3. Define a Verifier: Subclass JWTClaimVerifierBase. Implement verifyValue:withTrustedValue: to perform custom validation logic comparing the untrusted claim value against a trusted reference.
    /// 1. Define a claim
    @interface JWTClaimCustomIntersectionOfIntervals : JWTClaimBase
    @end
    
    @implementation JWTClaimCustomIntersectionOfIntervals
    + (NSString *)name { return @"intersectionOfIntervals"; }
    @end
    
    /// 2. Define a serializer
    @interface JWTClaimSerializerForInterval : JWTClaimSerializerBase
    @end
    
    @implementation JWTClaimSerializerForInterval
    - (NSObject *)deserializedClaimValue:(NSObject *)value forName:(NSString *)name {
        // Convert JSON string "1,5" to NSArray @[@1, @5]
    }
    - (NSObject *)serializedClaimValue:(id<JWTClaimProtocol>)claim {
        // Convert NSArray @[@1, @5] to JSON string "1,5"
    }
    @end
    
    /// 3. Define a verifier
    @interface JWTClaimVerifierForIntersection : JWTClaimVerifierBase
    @end
    
    @implementation JWTClaimVerifierForIntersection
    - (BOOL)verifyValue:(NSObject *)value withTrustedValue:(NSObject *)trustedValue {
        // Custom logic to check if 'value' is valid relative to 'trustedValue'
    }
    @end
  6. Load and use keys from PEM files

    master

    You can load RSA or EC keys directly from PEM files to use for signing and verification. Use JWTCryptoKeyExtractor to determine the key type from the PEM string.

    NSString *algorithmName = @"RS256";
    
    // Setup sign holder using PEM string
    id <JWTAlgorithmDataHolderProtocol> signDataHolder = [JWTAlgorithmRSFamilyDataHolder new]
        .keyExtractorType([JWTCryptoKeyExtractor privateKeyWithPEMBase64].type)
        .privateKeyCertificatePassphrase(passphrase)
        .algorithmName(algorithmName)
        .secret(privateKeyPemString);
    
    // Setup verify holder using PEM string
    id <JWTAlgorithmDataHolderProtocol> verifyDataHolder = [JWTAlgorithmRSFamilyDataHolder new]
        .keyExtractorType([JWTCryptoKeyExtractor publicKeyWithPEMBase64].type)
        .algorithmName(algorithmName)
        .secret(publicKeyPemString);
    
    // Sign
    JWTCodingBuilder *signBuilder = [JWTEncodingBuilder encodePayload:payloadDictionary].addHolder(signDataHolder);
    NSString *token = signBuilder.result.successResult.encoded;
    
    // Verify
    JWTCodingBuilder *verifyBuilder = [JWTDecodingBuilder decodeMessage:token].addHolder(verifyDataHolder);
    if (verifyBuilder.result.successResult) {
        NSDictionary *payload = verifyBuilder.result.successResult.payload;
    }
    id <JWTAlgorithmDataHolderProtocol> signDataHolder = [JWTAlgorithmRSFamilyDataHolder new].keyExtractorType([JWTCryptoKeyExtractor privateKeyWithPEMBase64].type).privateKeyCertificatePassphrase(passphrase).algorithmName(algorithmName).secret(privateKey);
  7. Install JWT via CocoaPods or Carthage

    master

    To use this library in your Objective-C project, add it to your dependency manager:

    CocoaPods Add this to your Podfile:

    pod "JWT"

    Carthage Add this to your Cartfile:

    github "yourkarma/JWT" "master"

    After installation, include the library in your source files using:

    @import JWT;
    // or
    #import <JWT/JWT.h>
    pod "JWT"
    github "yourkarma/JWT" "master"
    import JWT
  8. Encode and Decode JWTs with Custom Claims

    master

    To ensure custom claims are correctly processed during the JWT lifecycle, you must pass the JWTClaimsSetCoordinator to the JWTEncodingBuilder and JWTDecodingBuilder.

    Encoding:

    Pass the coordinator to encodeClaimsSetWithCoordinator:. This ensures the builder uses your custom serializers to transform the claimsSetStorage into the final JSON payload.

    Decoding:

    Pass the coordinator to decodeMessage:. This ensures the decoder uses your custom serializers to transform JSON values back into your local object types and uses your custom verifiers to validate the claims.

    // --- ENCODING ---
    // 1. Prepare holder (algorithm/secret)
    id<JWTAlgorithmDataHolderProtocol> holder = [JWTAlgorithmHSFamilyDataHolder new].algorithmName(@"HS384").secret(@"secret");
    
    // 2. Encode using the coordinator
    JWTCodingResultType *result = [JWTEncodingBuilder encodeClaimsSetWithCoordinator:claimsSetCoordinator]
        .headers(@{@"custom":@"value"})
        .addHolder(holder)
        .result;
    
    NSString *encodedToken = result.successResult.encoded;
    
    // --- DECODING ---
    // 1. Prepare decoding options
    NSNumber *options = @(JWTCodingDecodingOptionsNone);
    
    // 2. Decode using the coordinator
    JWTCodingResultType *decodedResult = [JWTDecodingBuilder decodeMessage:encodedToken]
        .claimsSetCoordinator(claimsSetCoordinator)
        .addHolder(holder)
        .options(options)
        .and.result;
    
    if (decodedResult.successResult) {
        // Access the deserialized custom objects
        id deserializedClaims = decodedResult.successResult.claimsSetStorage;
    }
  9. Encode and Decode JWTs using the Fluent API

    master

    The modern way to encode and decode tokens is using the JWTEncodingBuilder and JWTDecodingBuilder with JWTCodingResultType to handle success or error states.

    Encoding a ClaimsSet

    JWTClaimsSet *claimsSet = [[JWTClaimsSet alloc] init];
    claimsSet.issuer = @"Facebook";
    claimsSet.subject = @"Token";
    
    id<JWTAlgorithmDataHolderProtocol> holder = [JWTAlgorithmHSFamilyDataHolder new].algorithmName(@"HS384").secret(@"secret");
    
    JWTCodingResultType *result = [JWTEncodingBuilder encodeClaimsSet:claimsSet].headers(@{@"custom":@"value"}).addHolder(holder).result;
    
    if (result.successResult) {
        NSString *token = result.successResult.encoded;
    }

    Decoding a Token

    JWTCodingResultType *decodedResult = [JWTDecodingBuilder decodeMessage:yourJwt].claimsSet(claimsSet).addHolder(holder).options(@(JWTCodingDecodingOptionsNone)).and.result;
    
    if (decodedResult.successResult) {
        NSDictionary *payload = decodedResult.successResult.payload;
        NSDictionary *headers = decodedResult.successResult.headers;
    } else {
        NSError *error = decodedResult.errorResult.error;
    }
    JWTCodingResultType *result = [JWTEncodingBuilder encodeClaimsSet:claimsSet].headers(headers).addHolder(holder).result;
    JWTCodingResultType *decodedResult = [JWTDecodingBuilder decodeMessage:yourJwt].claimsSet(claimsSet).addHolder(holder).options(options).and.result;
  10. Use RS256 with Private/Public Keys

    master

    For RS256, encoding requires a private key (often from a .p12 file) and an optional passphrase. Decoding requires the corresponding public key.

    ```objective-c
    // Encode with Private Key
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@
  11. Implement Custom Claims

    master

    The old ClaimsSet API is deprecated and will be removed in version 3.0. To implement custom claims, follow this process:

    1. Define a custom serializer for the claim.
    2. Define a custom verifier for the claim.
    3. Register the new claim with the serializer and verifier using a JWTClaimsSetCoordinator.

    Example implementation:

    // Setup ClaimsSetCoordinator
    __auto_type claim = JWTClaimVariations.intersectionOfIntervals;
    __auto_type claimSerializer = JWTClaimSerializerVariations.interval;
    __auto_type claimVerifier = JWTClaimVerifierVariations.intersection;
    
    id<JWTClaimsSetCoordinatorProtocol> claimsSetCoordinator = [JWTClaimsSetCoordinatorBase new];
    [claimsSetCoordinator registerClaim:claim serializer:claimSerializer verifier:claimVerifier forClaimName:JWTClaimsNames.intersectionOfIntervals];
    
    // Configure and use
    __auto_type deserialized = ({
        claimsSetCoordinator.configureClaimsSet(^JWTClaimsSetDSLBase *(JWTClaimsSetDSLBase *claimsSetDSL) {
            claimsSetDSL.intersection = @[@(2), @(5)];
            return claimsSetDSL;
        });
        claimsSetCoordinator.claimsSetStorage;
    });
    
    // Serialize back to dictionary
    __auto_type dictionary = [claimsSetCoordinator.claimsSetSerializer dictionaryFromClaimsSet:deserialized];
    id<JWTClaimsSetCoordinatorProtocol> claimsSetCoordinator = [JWTClaimsSetCoordinatorBase new];
    [claimsSetCoordinator registerClaim:claim serializer:claimSerializer verifier:claimVerifier forClaimName:JWTClaimsNames.intersectionOfIntervals];
  12. Encode a JWT using JWTClaimsSet

    master

    For tokens using reserved claim names (like issuer, subject, audience), use [JWTBuilder encodeClaimsSet:]. All properties on the JWTClaimsSet are optional.

    ```objective-c
    JWTClaimsSet *claimsSet = [[JWTClaimsSet alloc] init];
    claimsSet.issuer = @"Facebook";
    claimsSet.subject = @"Token";
    claimsSet.audience = @"http://yourkarma.com";
    claimsSet.expirationDate = [NSDate distantFuture];
    claimsSet.notBeforeDate = [NSDate distantPast];
    claimsSet.issuedAt = [NSDate date];
    claimsSet.identifier = @"thisisunique";
    claimsSet.type = @"test";
    
    NSString *secret = @"secret";
    id<JWTAlgorithm> algorithm = [JWTAlgorithmFactory algorithmByName:@